楚慧杯Misc全解
楚慧杯Misc全解
gamego
先双击安装游戏
查看data
发现是RPGmaker
.rvdata2 文件是 RPG Maker VX Ace 游戏的数据文件,需要使用 RPG Maker VX Ace 或 Ruby 脚本
直接dump出所有文本
require 'zlib'
# --- Stub Classes for RGSS ---
class Table
def self._load(s); new; end
end
class Color
def self._load(s); new; end
end
class Tone
def self._load(s); new; end
end
module RPG
class Map
attr_accessor :display_name, :events, :width, :height, :data, :tileset_id, :bgm, :bgs, :autoplay_bgm, :autoplay_bgs, :encounter_list, :encounter_step, :parallax_name, :parallax_loop_x, :parallax_loop_y, :parallax_sx, :parallax_sy, :note
end
class Event
attr_accessor :id, :name, :x, :y, :pages
end
class Event::Page
attr_accessor :condition, :graphic, :move_type, :move_speed, :move_frequency, :move_route, :walk_anime, :step_anime, :direction_fix, :through, :priority_type, :trigger, :list
end
class EventCommand
attr_accessor :code, :indent, :parameters
end
class MoveRoute; attr_accessor :repeat, :skippable, :wait, :list; end
class MoveCommand; attr_accessor :code, :parameters; end
class BGM; attr_accessor :name, :volume, :pitch; end
class BGS; attr_accessor :name, :volume, :pitch; end
class SE; attr_accessor :name, :volume, :pitch; end
class ME; attr_accessor :name, :volume, :pitch; end
class System; attr_accessor :game_title, :version_id, :japanese, :party_members, :currency_unit, :elements, :skill_types, :weapon_types, :armor_types, :switches, :variables, :boat, :ship, :airship, :title_bgm, :battle_bgm, :battle_end_me, :gameover_me, :sounds, :test_battlers, :test_troop_id, :start_map_id, :start_x, :start_y, :terms, :opt_draw_title, :opt_use_midi, :opt_transparent, :opt_followers, :opt_slip_death, :opt_floor_death, :opt_display_tp, :magic_number, :window_tone; end
class System::Terms; attr_accessor :basic, :params, :etypes, :commands; end
class System::TestBattler; attr_accessor :actor_id, :level, :equips; end
class System::Vehicle; attr_accessor :character_name, :character_index, :bgm, :start_map_id, :start_x, :start_y; end
class BaseItem; attr_accessor :id, :name, :icon_index, :description, :features, :note; end
class UsableItem < BaseItem; attr_accessor :scope, :occasion, :speed, :success_rate, :repeats, :tp_gain, :hit_type, :animation_id, :damage, :effects; end
class Skill < UsableItem; attr_accessor :mp_cost, :tp_cost, :message1, :message2, :required_wtype_id1, :required_wtype_id2; end
class Item < UsableItem; attr_accessor :itype_id, :price, :consumable; end
class EquipItem < BaseItem; attr_accessor :price, :etype_id, :params; end
class Weapon < EquipItem; attr_accessor :wtype_id, :animation_id; end
class Armor < EquipItem; attr_accessor :atype_id; end
class Enemy < BaseItem; attr_accessor :battler_name, :battler_hue, :params, :exp, :gold, :drop_items, :actions; end
class Enemy::DropItem; attr_accessor :kind, :data_id, :denominator; end
class Enemy::Action; attr_accessor :skill_id, :condition_type, :condition_param1, :condition_param2, :rating; end
class State < BaseItem; attr_accessor :restriction, :priority, :remove_at_battle_end, :remove_by_restriction, :auto_removal_timing, :min_turns, :max_turns, :remove_by_damage, :chance_by_damage, :remove_by_walking, :steps_to_remove, :message1, :message2, :message3, :message4; end
class Troop; attr_accessor :id, :name, :members, :pages; end
class Troop::Member; attr_accessor :enemy_id, :x, :y, :hidden; end
class Troop::Page; attr_accessor :condition, :span, :list; end
class Troop::Page::Condition; attr_accessor :turn_ending, :turn_valid, :turn_a, :turn_b, :enemy_valid, :enemy_index, :enemy_hp, :actor_valid, :actor_id, :actor_hp, :switch_valid, :switch_id; end
class Animation; attr_accessor :id, :name, :animation1_name, :animation1_hue, :animation2_name, :animation2_hue, :position, :frame_max, :frames, :timings; end
class Animation::Frame; attr_accessor :cell_max, :cell_data; end
class Animation::Timing; attr_accessor :frame, :se, :flash_scope, :flash_color, :flash_duration; end
class CommonEvent; attr_accessor :id, :name, :trigger, :switch_id, :list; end
class Tileset; attr_accessor :id, :name, :mode, :tileset_names, :flags, :note; end
class AudioFile; attr_accessor :name, :volume, :pitch; end
class Class; attr_accessor :id, :name, :exp_params, :params, :learnings, :features, :note; end
class Class::Learning; attr_accessor :level, :skill_id, :note; end
class Actor; attr_accessor :id, :name, :nickname, :class_id, :initial_level, :max_level, :character_name, :character_index, :face_name, :face_index, :equips, :description, :features, :note; end
end
# --- Analysis Logic ---
def search_object(obj, path, visited = {})
# Avoid infinite recursion
return if visited[obj.object_id]
visited[obj.object_id] = true
case obj
when String
# Handle encoding
str = obj.dup
str.force_encoding('UTF-8') rescue str.force_encoding('ASCII-8BIT')
if str.match(/DASCTF\{.*?\}/i)
puts "\n\n!!!!! FOUND FLAG in #{path}: #{str} !!!!!\n\n"
elsif str.downcase.include?("dasctf")
puts "Found 'dasctf' in #{path}: #{str.inspect}"
elsif str.downcase.include?("getflag")
puts "Found 'getflag' in #{path}: #{str.inspect}"
end
when Array
obj.each_with_index do |item, index|
search_object(item, "#{path}[#{index}]", visited)
end
when Hash
obj.each do |key, value|
search_object(key, "#{path}[key:#{key}]", visited)
search_object(value, "#{path}[#{key}]", visited)
end
when RPG::EventCommand
# Special handling for Event Commands to show context
obj.parameters.each_with_index do |param, i|
search_object(param, "#{path}.parameters[#{i}]", visited)
end
when Object
# For generic objects, search instance variables
obj.instance_variables.each do |var|
value = obj.instance_variable_get(var)
search_object(value, "#{path}.#{var}", visited)
end
end
end
def decompress_scripts(scripts)
decompressed_scripts = []
scripts.each do |script|
id, name, code = script
begin
decompressed_code = Zlib::Inflate.inflate(code)
decompressed_scripts << [id, name, decompressed_code]
rescue Zlib::Error
# Maybe not compressed or other error
decompressed_scripts << [id, name, code]
end
end
decompressed_scripts
end
def dump_strings(obj, path, visited = {}, file = nil)
return if visited[obj.object_id]
visited[obj.object_id] = true
case obj
when String
str = obj.dup
str.force_encoding('UTF-8') rescue str.force_encoding('ASCII-8BIT')
msg = " String at #{path}: #{str.inspect}"
if file
file.puts msg
else
puts msg
end
when Array
obj.each_with_index { |item, i| dump_strings(item, "#{path}[#{i}]", visited, file) }
when Hash
obj.each { |k, v| dump_strings(k, "#{path}[key]", visited, file); dump_strings(v, "#{path}[#{k}]", visited, file) }
when Object
obj.instance_variables.each do |var|
dump_strings(obj.instance_variable_get(var), "#{path}.#{var}", visited, file)
end
end
end
# Dump all strings to a file
File.open("rvdata2_dump.txt", "w") do |file|
puts "Starting analysis... Outputting to rvdata2_dump.txt"
Dir.glob("*.rvdata2").each do |filename|
puts "Analyzing #{filename}..."
file.puts "\n\n=== FILE: #{filename} ===\n"
# 1. Raw binary search (Fallback method)
begin
raw_content = File.binread(filename)
# Search for all occurrences of DASCTF (case insensitive)
matches = raw_content.scan(/DASCTF.*?/i)
if !matches.empty?
file.puts "\n!!!!! FOUND POTENTIAL FLAG in #{filename} (Raw Search) !!!!!"
puts "\n!!!!! FOUND POTENTIAL FLAG in #{filename} (Raw Search) !!!!!"
# Print MORE context for each match
raw_content.scan(/.{0,100}DASCTF.{0,200}/im).each do |context|
file.puts "Context:\n#{context.inspect}\n"
puts "Context:\n#{context.inspect}\n"
end
file.puts "\n"
end
rescue => e
file.puts " Error reading file #{filename}: #{e}"
end
# 2. Object traversal search
begin
data = File.open(filename, "rb") { |f| Marshal.load(f) }
# Special handling for Scripts.rvdata2
if filename == "Scripts.rvdata2" && data.is_a?(Array)
file.puts " Decompressing scripts in #{filename}..."
data = decompress_scripts(data)
end
# Dump ALL strings for ALL files
dump_strings(data, filename, {}, file)
rescue StandardError => e
file.puts " Error loading #{filename}: #{e.message}"
end
end
end
puts "Analysis complete. Check rvdata2_dump.txt for full data."


得到
DASCTF{1168cb17-31ff-43b7--b586-8414d383afce}
Time_and_chaos_1
首先观察这些图片,肉眼看全是噪声,没什么规律。但是查看文件属性时发现,它们的修改时间 是不同的,而且不是顺序的。这很可能是一个排序的依据。
第一部分:Time Accumulation
这部分逻辑是把图片按照 st_mtime(修改时间)排序,然后提取每张图蓝色通道 (Blue Channel) 的第 6 位 (bit index 5),按照一定的衰减系数叠加。
第二部分:Bit Plane Reconstruction
这部分是把 8 张图按文件名顺序(1-8)排列,分别提取红通道 (Red Channel) 的第 6 位,然后把这 8 个 bit 拼成一个字节 (Byte)。。
import argparse
from pathlib import Path
import numpy as np
from PIL import Image
def solve_images():
# 设定输入目录
input_dir = Path(".")
files = [input_dir / f"{i}.png" for i in range(1, 9)]
# --- 任务 1: 生成 Accumulation 图 ---
# 关键点:按修改时间排序
ordered_by_time = sorted(files, key=lambda p: p.stat().st_mtime)
# 初始化累加器
first_img = np.array(Image.open(ordered_by_time[0]).convert("RGB"))
acc = np.zeros(first_img.shape[:2], dtype=np.float32)
decay = 0.85
inject = 0.15
for p in ordered_by_time:
img = np.array(Image.open(p).convert("RGB"))
# 提取 Blue 通道的第 6 bit (0x20)
bit = ((img[:,:,2] >> 5) & 1).astype(np.float32)
# 模拟时间衰减叠加
acc = acc * decay + bit * 255.0 * inject
# 保存结果
res_acc = np.clip(acc, 0, 255).astype(np.uint8)
Image.fromarray(res_acc, mode="L").save("solved_chaos.png")
print("[+] Generated solved_chaos.png")
# --- 任务 2: 生成 Rebuild 图 ---
# 关键点:按文件名顺序 1-8
imgs = [np.array(Image.open(p).convert("RGB")) for p in files]
# 提取 Red 通道的第 6 bit
bits = [((im[:,:,0] >> 5) & 1).astype(np.uint8) for im in imgs]
# 组合 bits: 1.png -> MSB, 8.png -> LSB
out = np.zeros(bits[0].shape, dtype=np.uint8)
for i, b in enumerate(bits):
out |= b << (7 - i)
# 归一化以便查看
out_f = out.astype(np.float32)
norm = ((out_f - out_f.min()) / (out_f.max() - out_f.min()) * 255.0).astype(np.uint8)
Image.fromarray(norm, mode="L").save("solved_rebuild.png")
print("[+] Generated solved_rebuild.png")
if __name__ == '__main__':
solve_images()

DASCTF{Logistic_and
接下来处理 flag.txt。打开文件看似只有一些毫无逻辑的文字(“也许是有用的东西”),中间夹杂着 "666"。
但是光标移动的时候感觉不对劲,有明显的卡顿,且文件大小比显示的字符要大。这显然是零宽字符隐写 (Zero-Width Steganography)。
with open("flag.txt", "r", encoding="utf-8") as f:
content = f.read()
# 打印非打印字符的 Unicode 编码
print([hex(ord(c)) for c in content if ord(c) > 0x2000])
零宽字符解密得到

_time_fly}
DASCTF{Logistic_and_time_fly}
老妈的故事书
解压拿到两个文件一个word一个pdf,在pdf尾巴发现提示
There are two positons that require 4,one is to crack is love,the other???
word密码:love

根据pdf里面的论文隐写方法可在简体/繁体之间切换的汉字

使用zhconv提取
import re
import zhconv
with open("document.xml", encoding="utf-8") as fp:
xml_raw = fp.read()
corpus = "".join(re.findall(r"<w:t[^>]*>([^<]*)</w:t>", xml_raw))
print(f"Total chars: {len(corpus)}")
markers = []
vi = 0
for c in corpus:
s_form = zhconv.convert(c, "zh-hans")
t_form = zhconv.convert(c, "zh-hant")
if s_form == c and t_form == c:
continue
if s_form != c:
markers.append(vi)
vi += 1
dists = [markers[0]]
for i in range(1, len(markers)):
dists.append(markers[i] - markers[i - 1] - 1)
print(f"Segments ({len(dists)}): {dists}")
nibbles = "".join(hex(n)[2:] for n in dists)
payload = bytes.fromhex(nibbles if len(nibbles) % 2 == 0 else nibbles[:-1])
print(f"Flag: {payload.decode('ascii', errors='replace')}")#!/usr/bin/env python3
"""
Chinese Text Steganography - HESM Extraction
pip install zhconv
"""
import os
from xml.etree import ElementTree as ET
import zhconv
DIR = os.path.dirname(os.path.abspath(__file__))
XML_PATH = os.path.join(DIR, "document.xml")
WML = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
def read_xml_text(path):
tree = ET.parse(path)
return "".join(node.text or "" for node in tree.iter(f"{WML}t"))
def extract_hesm(text):
segments = []
gap = -1
for ch in text:
to_s = zhconv.convert(ch, "zh-hans")
to_t = zhconv.convert(ch, "zh-hant")
if to_s != ch or to_t != ch:
gap += 1
if to_s != ch:
segments.append(gap)
gap = -1
return segments
def decode_segments(segs):
hex_str = "".join(format(v, "x") for v in segs)
raw = bytes(int(hex_str[i:i+2], 16) for i in range(0, len(hex_str) - len(hex_str) % 2, 2))
return hex_str, raw
def main():
text = read_xml_text(XML_PATH)
print(f"[*] Text length: {len(text)}")
segs = extract_hesm(text)
print(f"[*] {len(segs)} segments: {segs}")
hex_str, raw = decode_segments(segs)
print(f"[*] Hex: {hex_str}")
print(f"\n[+] Flag: {raw.decode('ascii', errors='replace')}")
if __name__ == "__main__":
main()
DASCTF{024bb015-5578-4181-9d28-e2f7d10bac4a}
generate_key
加载镜像
直接lovelymemLuxe的ai分析一波

找到三个有用的线索
generate_key 文件被IE浏览器打开
key.zip 文件被WinRAR打开
字符串:ASHFK4567315
分别用vol3导出这两个文件
两个都是加密zip
上面得到的ASHFK4567315可以作为密码解压generate_key,得到一个二进制文件
key.zip里有很多1kb的文件,可以猜到是crc爆破
4字节crc爆破
import zipfile
import zlib
import itertools
import string
import io
def crack_crc32(target_crc, length):
charset = string.ascii_letters + string.digits + string.punctuation + " "
for candidate in itertools.product(charset.encode(), repeat=length):
data = bytes(candidate)
if zlib.crc32(data) == target_crc:
return data.decode('utf-8', errors='ignore')
return None
def main():
zip_path = "key.zip"
results = {}
print("开始 CRC 爆破...")
with zipfile.ZipFile(zip_path, 'r') as z:
for info in z.infolist():
if info.filename.endswith('.zip'):
# 读取子 zip 文件的数据
sub_zip_data = z.read(info.filename)
with zipfile.ZipFile(io.BytesIO(sub_zip_data), 'r') as sub_z:
for sub_info in sub_z.infolist():
if sub_info.filename.endswith('.txt'):
target_crc = sub_info.CRC
size = sub_info.file_size
print(f"正在破解 {sub_info.filename} (CRC32: {hex(target_crc)}, Size: {size})...")
cracked_content = crack_crc32(target_crc, size)
if cracked_content:
print(f" -> 成功: {cracked_content}")
# 提取数字索引用于排序,例如 "10.txt" -> 10
index = int(sub_info.filename.split('.')[0])
results[index] = cracked_content
else:
print(f" -> 失败: 未找到匹配内容")
print("\n--- 最终结果 ---")
final_string = ""
# 按索引顺序拼接
for i in sorted(results.keys()):
print(f"{i}.txt: {results[i]}")
final_string += results[i]
print(f"\n拼接后的字符串: {final_string}")
with open("result.txt", "w") as f:
f.write(final_string)
print("结果已保存到 result.txt")
if __name__ == "__main__":
main()
U2FsdGVkX19N/id+O8PA1l9SuuQ4JiS7edG9Og8TaTXUIakkm1gHQ/X77iR4IpKy
得到一串PBE的密文
接着分析二进制文件
bash -lc readelf -x .rodata /mnt/data/generate_key && echo '---DATA---' && readelf -x .data /mnt/data/generate_key
程序在 .data 段里藏了一段初始字符串
ZIKT001NIKH7WZYGQWZZH
进制里有 3 个连续的变换函数,按这个顺序套上去:
bac,b5c,b1c
这三个函数的核心逻辑分别是:



总结来说就是
// bac
x = ((c ^ 0x5A) + 0x33 - (c & 0x0F)) & 0xFF
// b5c
x = ((c + 7) ^ (i % 2 ? 0x11 : 0x22)) & 0xFF
// b1c
x = c ^ 0x33
把 ZIKT001NIKH7WZYGQWZZH 依次做这三步后,得到的字节序列十六进制正好是
21 66 51 66 B5 86 B5 62 55 62 55 82 51 12 25 72 55 62 21 12 55
exp:
def bac(data: bytes) -> bytes:
out = bytearray()
for c in data:
x = ((c ^ 0x5A) + 0x33 - (c & 0x0F)) & 0xFF
out.append(x)
return bytes(out)
def b5c(data: bytes) -> bytes:
out = bytearray()
for i, c in enumerate(data):
x = ((c + 7) ^ (0x11 if (i % 2) else 0x22)) & 0xFF
out.append(x)
return bytes(out)
def b1c(data: bytes) -> bytes:
out = bytearray()
for c in data:
x = c ^ 0x33
out.append(x)
return bytes(out)
def main():
s = b"ZIKT001NIKH7WZYGQWZZH"
stage1 = bac(s)
stage2 = b5c(stage1)
stage3 = b1c(stage2)
print("origin :", s.decode())
print("stage1 :", stage1.hex().upper())
print("stage2 :", stage2.hex().upper())
print("key :", stage3.hex().upper())
if __name__ == "__main__":
main()
21665166B586B56255625582511225725562211255
得到
key为0x21665166b586b562556255825112257255622112550a
我们可以
openssl enc -d -aes-256-cbc -a -md md5 -p
或者直接

得到
DASCTF{HEEEKAHSZKH26}
SAM_and_Steg
从题目名 SAM_and_Steg 就能先想到两条线:
SAM + SYSTEM可以用来做 Windows 本地账户信息提取Steg提示文件里可能还藏了隐写或额外数据
hexdump -C system | tail
这时候会发现文件尾部不是很像正常的 hive 结束方式,说明后面可能拼接了别的东西。
grep/xxd 找偏移
xxd -p system | tr -d '\n' | grep -abo 'ffd8ff'
发现jpg图片,提取出来
with open("system", "rb") as f:
data = f.read()
start = data.rfind(b"\xff\xd8\xff")
end = data.find(b"\xff\xd9", start) + 2
with open("out.jpg", "wb") as f:
f.write(data[start:end])

SYSTEM 和 SAM hive 离线提取后,拿到管理员这一条哈希
Administrator:500:aad3b435b51404eeaad3b435b51404ee:476b4dddbbffde29e739b618580adb1e:::
hashcat爆破

得到
!checkerboard1
用SilentEye解决jpg隐写

得到AES256
然后拿文件之前找到的p@s4w0rd 解密

openssl enc -d -aes-256-cbc -in AES256 -out aes.dec -pass pass:p@s4w0rd
最后再解压得到flag

DASCTF{aa28f51d-0f54-4286-af3c-86a14fbab4a4}
本文来自博客园,作者:{Alexander17},转载请注明原文链接:{https://home.cnblogs.com/u/alexander17}

浙公网安备 33010602011771号