diff --git "a/crypto/[GCCCTF 2025]\344\274\212\350\216\253\351\270\241.md" "b/crypto/[GCCCTF 2025]\344\274\212\350\216\253\351\270\241.md" new file mode 100644 index 0000000..be98aa1 --- /dev/null +++ "b/crypto/[GCCCTF 2025]\344\274\212\350\216\253\351\270\241.md" @@ -0,0 +1,51 @@ +## 基本信息 + +- 题目名称: [GCCCTF 2025]伊莫鸡 +- 考点清单: HTML实体编码、换表base64 + +## 一、看到什么 + +全是表情符号,还有一串64个不重复的字符表 + +## 二、想到什么解题思路 + +表情符号可以想到base100,64个不重复的字符表可能是换表base64的对应的表 + +## 三、尝试过程和结果记录 + +表情符号在网站上解密可以得到第一个字符串,base100 +给出两串字符串: + +1. `lWEQ...;yW` —— HTML 实体编码。 +2. `a6pQqIUurcibPENJSlHxjZkydT3tgY4oFeDXL5mf1V+zR7wv9CnBWh/M2sOAG80K` —— 64 个字符且不重复。 + 提示说明第二串为“非标准 Base64”,意味着它是**自定义 Base64 字母表**。 + +**解题思路** + +1. 首先将 HTML 实体转回字符,得到: + `lWEQShlU4hgBPjP9xxEoEB6oELEQSBYUyBr9PXjeryW`。 +2. 观察第二串长度正好 64,推测为自定义 Base64 字母表。 + 标准 Base64 表为:`A–Z a–z 0–9 + /`。 +3. 将自定义表的每个字符按索引映射回标准 Base64 表。 +4. 对第一串中字符逐一替换为对应标准 Base64 字符。 +5. 对替换结果做标准 Base64 解码,得到明文。 + +**关键代码** + +```python +import base64, html +html_text = "lWEQShlU4hgBPjP9xxEoEB6oELEQSBYUyBr9PXjeryW" +custom = "a6pQqIUurcibPENJSlHxjZkydT3tgY4oFeDXL5mf1V+zR7wv9CnBWh/M2sOAG80K" +standard = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +mapping = {custom[i]: standard[i] for i in range(64)} +decoded = html.unescape(html_text) +mapped = "".join(mapping.get(ch, ch) for ch in decoded) +print(base64.b64decode(mapped + "=" * ((4 - len(mapped) % 4) % 4)).decode()) +``` + +**flag** + +``` +GCCCTF{W31C0M3_70_6CCC7F_2025!!} +``` + diff --git "a/crypto/[GCCCTF 2025]\345\257\206\351\222\245\345\215\261\346\234\272.md" "b/crypto/[GCCCTF 2025]\345\257\206\351\222\245\345\215\261\346\234\272.md" new file mode 100644 index 0000000..b4da85e --- /dev/null +++ "b/crypto/[GCCCTF 2025]\345\257\206\351\222\245\345\215\261\346\234\272.md" @@ -0,0 +1,323 @@ +## 基本信息 + +- 题目名称: [GCCCTF 2025]密钥危机 +- 考点清单: RSA共享质因子攻击 + +## 一、看到什么 + +这是一个RSA共享质因子攻击题目。题目给出了10个RSA密钥和对应的密文。 + +## 二、想到什么解题思路 + +对所有RSA模数进行两两GCD计算,然后共享质因子攻击 + +## 三、尝试过程和结果记录 + +1. 对所有RSA模数进行两两GCD计算,发现密钥3和密钥7共享质因子 +2. 通过GCD分解得到共享的质因子p,进而计算出q = n/p +3. 计算欧拉函数φ(n) = (p-1)(q-1)和私钥d = e^(-1) mod φ(n) +4. 使用私钥解密密文得到flag + +## EXP + +**找到共享密钥对:** + +```python +#!/usr/bin/env python3 +# find_shared_pairs.py +# 用法: +# python find_shared_pairs.py data.json +# +# 功能: +# - 解析JSON格式的RSA数据文件,检测密钥对之间的关联性 +# - 识别共享模数对 (n相同,可用于共模攻击) +# - 识别共享质因子对 (有公共质因子,可用于因子分解攻击) +# - 将结果保存到 candidates.txt 供后续攻击使用 + +import sys +import json +import gmpy2 +from gmpy2 import mpz + +def parse_int(s): + """ + 解析整数字符串,支持十六进制(0x开头)和十进制 + 参数: s - 字符串输入 + 返回: gmpy2的mpz多精度整数对象 + """ + s = s.strip() + if s.startswith("0x") or s.startswith("0X"): + return mpz(s, 16) # 十六进制转换 + return mpz(s) # 十进制转换 + +def load_data(path): + """ + 加载并解析JSON格式的RSA数据文件 + 文件格式: JSON对象,每个键为索引,值为包含n、e、c的对象 + 参数: path - JSON数据文件路径 + 返回: 字典 {索引: (模数n, 指数e, 密文c)} + """ + try: + with open(path, 'r', encoding='utf-8') as f: + data = json.load(f) + except Exception as exc: + print(f"错误: 无法加载JSON文件 {path}: {exc}") + return {} + + pubs = {} + for idx_str, key_data in data.items(): + try: + idx = int(idx_str) + n = parse_int(key_data["n"]) + e = parse_int(key_data["e"]) + c = parse_int(key_data["c"]) + pubs[idx] = (n, e, c) + except (ValueError, KeyError) as exc: + print(f"警告: 索引 {idx_str} 数据解析失败: {exc}") + continue + + return pubs + +def find_pairs(pubs): + """ + 查找RSA公钥之间的潜在攻击对 + 检测两种类型的关联: + 1. 相等模数对: n1 == n2 (共模攻击候选) + 2. 共享质因子对: gcd(n1, n2) > 1 (共享因子攻击候选) + + 参数: pubs - 公钥字典 + 返回: (相等模数对列表, 共享质因子对列表) + """ + items = sorted(pubs.items()) # 按索引排序所有公钥 + equal_pairs = [] # 存储相同模数的密钥对 + gcd_pairs = [] # 存储有共享质因子的密钥对 + n_to_idxs = {} # 模数 -> 索引列表的映射 + + # 步骤1: 快速检测相同模数的密钥 + # 构建模数到索引的映射,便于检测重复 + for idx, (n, e, c) in items: + key = int(n) # 转换为标准整数用于映射 + n_to_idxs.setdefault(key, []).append(idx) + + # 检查每个模数值,如果对应多个索引,说明有相同模数 + for key, idxs in n_to_idxs.items(): + if len(idxs) > 1: # 如果有多个密钥使用相同模数 + # 生成所有可能的密钥对组合 + for i in range(len(idxs)): + for j in range(i+1, len(idxs)): + equal_pairs.append((idxs[i], idxs[j], mpz(key))) + + # 步骤2: 检测共享质因子 (只检查模数不同的密钥对) + # 获取所有模数及其索引 + n_list = [(idx, pubs[idx][0]) for idx in sorted(pubs.keys())] + N = len(n_list) + + # 逐一比较所有密钥对的模数 + for i in range(N): + idx_i, n_i = n_list[i] + for j in range(i+1, N): # 避免重复检测同一对 + idx_j, n_j = n_list[j] + if n_i == n_j: # 跳过相同模数的对 (已在步骤1处理) + continue + # 计算两个模数的最大公约数 + g = gmpy2.gcd(n_i, n_j) + if g != 1: # 如果gcd > 1,说明有共享质因子 + gcd_pairs.append((idx_i, idx_j, int(g))) + + return equal_pairs, gcd_pairs + +def main(): + """ + 主函数 - 执行RSA密钥对分析的主流程 + 检测共享攻击的候选对并将结果保存到文件 + """ + # 检查命令行参数 - 需要JSON数据文件路径 + if len(sys.argv) < 2: + print("用法: python find_shared_pairs.py data.json") + print("说明: data.json 文件应包含JSON格式的RSA数据") + sys.exit(1) + + data_file = sys.argv[1] + print(f"正在加载JSON数据文件: {data_file}") + + # 加载并解析JSON数据文件 + pubs = load_data(data_file) + if not pubs: + print("错误: 未能加载任何数据,请检查JSON文件格式。") + sys.exit(1) + + print(f"成功加载 {len(pubs)} 个RSA密钥") + + # 执行密钥对分析 + print("开始分析RSA密钥对的关联性...") + equal_pairs, gcd_pairs = find_pairs(pubs) + + # 将分析结果保存到 candidates.txt + with open("candidates.txt", "w", encoding='utf-8') as out: + + # 处理相同模数的密钥对 (共模攻击候选项) + if equal_pairs: + print("\n=== 相同模数密钥对 (共模攻击候选项) ===") + out.write("=== equal ===\n") + for a, b, n in equal_pairs: + bit_length = n.bit_length() + print(f"相同模数: 密钥a={a} <-> 密钥b={b} ; mod长度={bit_length}位") + out.write(f"equal {a} {b} {int(n)}\n") + else: + print("\n未发现相同模数的密钥对") + out.write("=== equal ===\n") + + # 处理共享质因子的密钥对 (共享因子攻击候选项) + if gcd_pairs: + print("\n=== 共享质因子密钥对 (共享因子攻击候选项) ===") + out.write("=== gcd > 1 ===\n") + for a, b, g in gcd_pairs: + bits = g.bit_length() + print(f"共享质因子: 密钥a={a} <-> 密钥b={b} ; 公因子大小={g} ; 公因子长度={bits}位") + out.write(f"gcd {a} {b} {g}\n") + else: + print("\n未发现共享质因子的密钥对") + out.write("=== gcd > 1 ===\n") + + print(f"\n分析结果已保存到 candidates.txt") + print("这些候选项可用于进一步的密码分析攻击") + +if __name__ == "__main__": + main() + +``` + +**共享质因子攻击** + +```python +#!/usr/bin/env python3 +# exploit_shared_factor_hardcoded.py +# 说明: +# - 将来自 find_shared_pairs.py 的候选对信息 (n,e,c) 直接硬编码到 main()。 +# - 支持 n/e/c 使用十进制字符串或以 0x 开头的十六进制字符串(将它们作为 Python 字面量粘贴即可)。 +# - 运行: python exploit_shared_factor_hardcoded.py +# +# 例: 如果你之前得到 "gcd 1 7 12345..." 并从 pubs/ciphers 中提取了对应的 n,e,c, +# 把这些值替换到下面的 REPLACE 段即可。 + +import gmpy2 +from gmpy2 import mpz +from Crypto.Util.number import inverse + + +def int_to_bytes(i): + if i == 0: + return b'\x00' + bl = (i.bit_length() + 7) // 8 + return int(i).to_bytes(bl, 'big') + +def extract_flag_from_bytes(b): + try: + s = b.decode(errors='ignore') + except: + s = '' + idx = s.find('flag{') + if idx != -1: + end = s.find('}', idx) + if end != -1: + return s[idx:end+1] + else: + return s[idx: idx+200] + idx2 = b.find(b'flag{') + if idx2 != -1: + end = b.find(b'}', idx2) + if end != -1: + return b[idx2:end+1].decode(errors='ignore') + else: + return b[idx2: idx2+200].decode(errors='ignore') + return None + +def decrypt_with_p(n_val, e_val, c_val, p_val): + """ + 给定 n, e, c 和已知共享因子 p,计算 q = n/p, phi, d, 并解密返回 (m_int, m_bytes). + 可能抛出异常(如 e 与 phi 不互素)。 + """ + n = int(n_val); p = int(p_val) + if n % p != 0: + raise ValueError("p does not divide n") + q = n // p + phi = (p - 1) * (q - 1) + if gmpy2.gcd(e_val, phi) != 1: + raise ValueError("e and phi not coprime") + d = int(inverse(int(e_val), int(phi))) + m = pow(int(c_val), d, n) + mb = int_to_bytes(m) + return m, mb, q, d + +def main(): + # ========================== + # <<< REPLACE HERE (BEGIN) >>> + # + # 将下面的示例值替换为你实际的 n/e/c(来自 pubs.txt 与 ciphers.txt) + # 直接写入整数值 + # + # Example (replace these three lines): + n1 = 728475864362644081347540315873884245170512135701066557294924499972329632560414952570969815858852324848031638385171644501279168982985817887185542141133287103807584084378738053730344187687749167394069025301540727509854910376898286000165865316799114020562490439573697322678113097460290058631832853044688653609198547660899929568972226083117169635043416263267128395154740005380826302549777138079935172020941455490451268594172430581670884908225402501025433560315093029311711899451326621770249682076855977027672494609011873096975675686689260176251943838451246788950331255313650764685316343322880607502462156930773310205639601957971212427635464209935091319994330114398413139442390011164422642488149538911544171530182458794684140725919261998092331634993866179888195021304669171509489945233003488582438140428511327128660957388078976021292899458053442354791525871730152298011718502911015360863551175027742427854663748817203568634597773125326947412679198173678082369546878866289155880424550657161482939524887398313368060507195321065456844998488475628170518007153922697221050361107581124955354262883799522334445506809154783358522775521844802117001082068223699724036790195661517438410464362601199211534769900458074569001718947546352542825613952583 + e1 = 11529429660049272989 + c1 = 296917052617552865327169025000845245893727427526867381895847288295495663934200301968569479168329533679413589297627017035724323228629577536091280999631117455903904772618580023616880002095555849373940587236981377708130140577983365947320683948408224617878422399122659997764082725576340098434673871805314621053517576130075450839029295592365648596144974335677476231368272498140460129920084785253501973941771845233049332507268156618951448267464582801685226385910636385309853607794579570517856383366004677874399922803136217301021932775942471296101992539707800285453741668716768076169954191554504093868506406062718913982195053434765110647258298829115429755981696496801843048710764526042131675565449422311568972175652850419501285960676034609216207002320442904340892097802835380924988694006581427925356029192568134027860681083646766454220093773266333147960566994083746286469680390842877690775425507657042118888669032396853100123607926390970979003229647692292281468308118730065625541852003261738149119943097131811370920509236747060789764090596471700345077347737377713834153568756053856959434792737056680866903861761075097925469065280665616056307463913084750651273175438110235570545986104012506239424733824102924733776025183545879591340056512148 + # + n2 = 832028278114237963325471730843378102605908639169606681113491863080296901810958485454060210094962830534704563612759500136235787440496520167823757979416085883428095569636313521920352975394490822668602890282963276037226717226295182338508813820574371626622629434213411698676913434780469715860112376630100334997302639817993501536634945837552441049569626248913610420034695576877357027249090102914696997570561964743914738853779696439091951907930371699310929261470151991289709941324574901831629899437676305269815176326745283978194393601880544239884579577912717278302755132709545423277112152192587519680861642720183934506926248053809213422706693338425474122160720392032194222899198495060572462775212866763421630916340418094741956702066104941203638241386978601001623399335036452282072599858908452038787728161344812642388259459109832368735726063200937374275862470022142657739223091854951937750284633871884198069973793069526130464370910605058705786287799934240731189059202394049501508632128995478685606589516288628232858199689621840389345706945843914223593147694280538587371673694827210446917994190486873993619575793894685592696689152894637471144857997471260537840753910570831259131816976190933658408869612465346500696175085829152729759107463853 + e2 = 12921361841041067353 + c2 = 205166198053712145777306438747950517251446834541432659696314132323463045358754719034844404062697430639788389842465874106503692328392259088527408560556346799583884212630483461057568412304778962546896445711177357005202598154388523047905966146298732990456541439043514137630864355160011049212765091217890331711672266681156558616709582144064870620769328153291178651994048506535646338685152296164868232369615001622988611036045975546350298955113443918190380501226294720778856906783467316118774791114893749366058082331643793101876321599252328326310398604488770990663360773679555215683516030036476425432051860321464083643734592636896555628678590127227754994237749790457902417625777897432446402576559336680179400734667888963340376505842665920569576278895334370259406235725127778038810947725570342422338547883699901141992837239442507740878642855466701661676642847101234918805132987314271231551675759223406461061608764314269535435038270402226418129848422338953596264459919910198026427028833524749035821102536110928562271976100309203074985704516632359415628295555764237523156337520885142867803490001635469107889897883078719722129746061078111359620349150669974515145537358798552069747445642715608520056015540292445530834356836037085783960798601298 + # + # <<< REPLACE HERE (END) >>> + # ========================== + + # 转换为mpz + n1_mp = mpz(n1) + e1_mp = mpz(e1) + c1_mp = mpz(c1) + n2_mp = mpz(n2) + e2_mp = mpz(e2) + c2_mp = mpz(c2) + + # 共享质因子已通过 find_shared_pairs.py 验证 + # 直接从 gcd(n1, n2) 获取共享质因子 + g = gmpy2.gcd(n1_mp, n2_mp) + p = int(g) + print(f"Shared factor p (bitlen={p.bit_length()}). Attempting to decrypt both ciphertexts...") + + # try decrypt side 1 + try: + m1_int, m1_bytes, q1, d1 = decrypt_with_p(n1_mp, e1_mp, c1_mp, p) + print("\n--- Side 1 (n1) decryption success ---") + print(f"q1 bitlen: {q1.bit_length()}, d1 bitlen: {d1.bit_length()}") + print("m1 (hex preview):", m1_bytes.hex()[:512] + ("..." if len(m1_bytes.hex())>512 else "")) + try: + decoded = m1_bytes.decode('utf-8', errors='ignore') + print("m1 (decoded):", repr(decoded)) + except Exception as e: + print("m1 (decoded error):", e) + print("m1 (raw bytes):", m1_bytes) + except Exception as ex: + print("\n--- Side 1 decryption failed ---") + print("Error:", ex) + + # try decrypt side 2 + try: + m2_int, m2_bytes, q2, d2 = decrypt_with_p(n2_mp, e2_mp, c2_mp, p) + print("\n--- Side 2 (n2) decryption success ---") + print(f"q2 bitlen: {q2.bit_length()}, d2 bitlen: {d2.bit_length()}") + print("m2 (hex preview):", m2_bytes.hex()[:512] + ("..." if len(m2_bytes.hex())>512 else "")) + try: + decoded = m2_bytes.decode('utf-8', errors='ignore') + print("m2 (decoded):", repr(decoded)) + except Exception as e: + print("m2 (decoded error):", e) + print("m2 (raw bytes):", m2_bytes) + except Exception as ex: + print("\n--- Side 2 decryption failed ---") + print("Error:", ex) + + print("\nDone.") + +if __name__ == "__main__": + main() + +``` + diff --git a/reverse/[GCCCTF 2025] constraint.md b/reverse/[GCCCTF 2025] constraint.md new file mode 100644 index 0000000..8f66df7 --- /dev/null +++ b/reverse/[GCCCTF 2025] constraint.md @@ -0,0 +1,220 @@ +## 基本信息 + +- 题目名称: [GCCCTF 2025] constraint +- 考点清单: 脱壳技术,约束求解 + +## 一、看到什么 + +这是一道综合性的Reverse题目,涉及壳识别、脱壳、静态分析和约束求解。题目给出一个Windows PE 64位可执行文件calme.exe。 + +## 二、想到什么解题思路 + +使用IDA Pro打开calme.exe,发现以下特征: + +1. 程序段名为 **UPX0**、**UPX1**、**UPX2** +2. 入口点函数非常复杂,包含大量位操作和解压逻辑 +3. 代码段中有大量0xFF填充 + +这些特征表明程序使用了 **UPX壳** 进行压缩。 + +## 三、尝试过程和结果记录 + +UPX是一个常见的可执行文件压缩工具,可以直接使用UPX工具进行解包: + +```bash +# 安装UPX(macOS) +brew install upx + +# 解包 +upx -d calme.exe -o calme_unpacked.exe +``` + +**解包结果**: + +- 原始大小:137KB (140592 bytes) +- 解包后:311KB (318256 bytes) +- 压缩率:44.18% + +解包成功后,在IDA Pro中打开`calme_unpacked.exe`进行分析。 + +### 静态分析 + +使用IDA Pro分析解包后的程序,找到两个关键函数: + +#### main函数 (0x4016ee) + +```c +int main(int argc, const char **argv, const char **envp) +{ + char Buffer[272]; + FILE *v3; + + printf("Enter the flag: "); + v3 = __acrt_iob_func(0); + + if (!fgets(Buffer, 256, v3)) + return 1; + + // 去除换行符 + size_t len = strlen(Buffer); + if (len > 0 && Buffer[len-1] == '\n') + Buffer[len-1] = '\0'; + + if (verify_flag(Buffer)) { + puts("Correct!"); + return 0; + } else { + puts("Wrong!"); + return 1; + } +} +``` + +#### verify_flag函数 (0x401560) + +这是核心验证函数,包含多个约束条件: + +```c +bool verify_flag(char* a1) +{ + unsigned char v5, v6, v7, v8, v9, v10, v11, v12; + + // 1. 检查长度:必须为16字符 + if (strlen(a1) != 16) + return false; + + // 2. 检查前缀:必须是"GCCCTF{" + if (memcmp(a1, "GCCCTF{", 7) != 0) + return false; + + // 3. 检查结尾:必须是'}' + if (a1[15] != '}') + return false; + + // 4. 提取8个字节 (flag[7]到flag[14]) + for (int i = 0; i < 8; i++) + *(&v5 + i) = a1[i + 7]; + + // 5. 线性方程组约束 + if (7*v5 + 3*v6 + 11*v7 + 13*v9 != 2145) + return false; + if (8*v6 + 12*v8 + 9*v10 + 4*v12 != 2491) + return false; + if (6*v5 + 5*v7 + 8*v11 + 7*v12 != 2299) + return false; + if (9*v8 + 11*v9 + 6*v10 + 10*v11 != 3165) + return false; + + // 6. 异或约束 + return (v10 ^ (v5 ^ v6 ^ v7 ^ v8 ^ v9)) == 95; +} +``` + +### 建立数学模型 + +从验证函数中提取出约束条件: + +**变量定义**: + +- Flag格式:`GCCCTF{xxxxxxxx}` +- 需要求解8个字节:v5, v6, v7, v8, v9, v10, v11, v12 + +**约束条件**: + +1. **线性方程组**(4个方程,8个未知数): + + ``` + 7*v5 + 3*v6 + 11*v7 + 0*v8 + 13*v9 + 0*v10 + 0*v11 + 0*v12 = 2145 + 0*v5 + 8*v6 + 0*v7 + 12*v8 + 0*v9 + 9*v10 + 0*v11 + 4*v12 = 2491 + 6*v5 + 0*v6 + 5*v7 + 0*v8 + 0*v9 + 0*v10 + 8*v11 + 7*v12 = 2299 + 0*v5 + 0*v6 + 0*v7 + 9*v8 + 11*v9 + 6*v10 + 10*v11 + 0*v12 = 3165 + ``` + +2. **异或约束**: + + ``` + v10 ^ (v5 ^ v6 ^ v7 ^ v8 ^ v9) = 95 + ``` + +3. **字符范围约束**: + + - 所有变量必须是可打印ASCII字符(33-126) + +### 编写求解脚本 + +使用Z3 SMT求解器来求解约束题目 + +```python +#!/usr/bin/env python3 +""" +CTF题目解题脚本 +使用Z3 SMT求解器来求解约束题目 +""" + +from z3 import * + +def solve_constraint_challenge(): + """使用Z3求解约束系统""" + + print("=== Constraint Solver ===") + + # 创建Z3求解器 + solver = Solver() + solver.set("timeout", 10000) + + # 定义8个整数变量 + b = [Int(f'b{i}') for i in range(8)] + + # 字符类型约束 + for i in range(8): + solver.add(Or( + And(b[i] >= 97, b[i] <= 122), # 小写字母 + And(b[i] >= 65, b[i] <= 90), # 大写字母 + And(b[i] >= 48, b[i] <= 57) # 数字 + )) + + # 线性约束(每个约束至少4个变量) + solver.add(7*b[0] + 3*b[1] + 11*b[2] + 13*b[4] == 2145) + solver.add(8*b[1] + 12*b[3] + 9*b[5] + 4*b[7] == 2491) + solver.add(6*b[0] + 5*b[2] + 8*b[6] + 7*b[7] == 2299) + solver.add(9*b[3] + 11*b[4] + 6*b[5] + 10*b[6] == 3165) + + # XOR约束(使用位操作约束,确保XOR约束是必需的) + # 为前6个字节的每一位创建布尔变量 + bits = [[Bool(f'bit_{i}_{j}') for j in range(8)] for i in range(6)] + + # 约束:位变量组合成字节值 + for i in range(6): + byte_val = Sum([If(bits[i][j], 2**j, 0) for j in range(8)]) + solver.add(b[i] == byte_val) + + # XOR约束:每一位的XOR结果等于目标值的对应位 + target_xor = 0x5f + target_bits = [bool(target_xor & (1 << j)) for j in range(8)] + for j in range(8): + bit_xor = bits[0][j] + for i in range(1, 6): + bit_xor = Xor(bit_xor, bits[i][j]) + solver.add(bit_xor == target_bits[j]) + + # 求解 + if solver.check() == sat: + model = solver.model() + flag_bytes = [model[b[i]].as_long() for i in range(8)] + flag_content = ''.join(chr(b) for b in flag_bytes) + return f"GCCCTF{{{flag_content}}}" + else: + return None + +def main(): + flag = solve_constraint_challenge() + if flag: + print(flag) + else: + print("No solution found") + +if __name__ == "__main__": + main() +``` + +flag: **GCCCTF{F14gH3re}** \ No newline at end of file diff --git "a/web/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221.md" "b/web/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221.md" new file mode 100644 index 0000000..993f75a --- /dev/null +++ "b/web/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221.md" @@ -0,0 +1,96 @@ +## 基本信息 + +- 题目名称: [GCCCTF 2025]守法公民 +- 考点清单: js分析,越权漏洞 + +## 一、看到什么 + +进到题目是一个网络安全法知识问答平台,给出提示达到10000分就可以解锁大奖,但是每题才一分,要是直接答题肯定是来不及的。 + +先注册一个账号: + +可以发现所有发送到后端的数据都是加密的,格式为: + +```json +{ + “data”:“xxxxxxxxxxxxxxxxxx” +} +``` + +## 二、想到什么解题思路 + +点开浏览器调试页面发现js代码混淆,我们要先分析加密逻辑。 + +## 三、尝试过程和结果记录 + +调试页面有反调试,可以通过“Nerver pause here”把这些debugger失效 + +![image-20251103105905177](images/[GCCCTF 2025]守法公民-1.png) + +尝试重置密码,然后发现有一个函数名aesEncrypt,尝试在此打断点 + +![image-20251103110250612](images/[GCCCTF 2025]守法公民-2.png) + +可以看到传入的数据是: + +``` +{\"user_id\":\"8f599954593c4970b28dcac52ff9df71\",\"new_password\":\"1\",\"confirm_password\":\"1\"} +``` + +可能是重置密码时需要带上本人id,以及重置的密码然后发送给后端,这里可能存在越权漏洞。 + +在这里发现了CryptoJS的加密部分: + +![image-20251103110605697](images/[GCCCTF 2025]守法公民-3.png) + +一般格式是这样的: + +``` + const ciphertext = CryptoJS.AES.encrypt(data, key, { + iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7 + }); +``` + +这里需要key,data,iv,mode,padding + +对照混淆代码看哪里是,然后去console打印 + +_`_0x2d4799`对应data,_`_0x41afe6`对应key,`_0x304595`对应iv,`CryptoJS['mode']['CBC']`就是mode,`CryptoJS['pad'][_0x3a81be(0x166)]`是padding + +依次去打印,注意iv和key是字节,可以base64编码一下再输出 + +![image-20251103111828675](images/[GCCCTF 2025]守法公民-4.png) + +然后就可以据此构造加密数据包修改管理员密码,现在还差一个管理员的uuid才能构造。 + +想到公告栏是管理员发的,查看网页源代码, + +![image-20251103112134612](images/[GCCCTF 2025]守法公民-5.png) + +由此可以开始构造包,先抓一个修改密码的包解密: + +```javascript +JSON.parse(CryptoJS.AES.decrypt("ReZVa6ElTwQSlWYY5IlkbKE6VFBNyMc4KROEd1eVTV4rDn2Y1lc5LB6hOPv2w1YGqviCVP1kQJPI9dWxtfl1dkBeew5PcKzqDxexmT/HpAyH8vLrr2JBgTLJq5XyzPFO", CryptoJS.enc.Base64.parse("Qqk39LaSxFml8cwZLxFNCk1NXun6i2nXebB2rouRgNA="), {iv: CryptoJS.enc.Base64.parse("F1wyy3xQ42vTbm2JZ+6yow=="),mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.Pkcs7}).toString(CryptoJS.enc.Utf8)); +``` + +然后修改uuid和password,构造以下加密包: + +```javascript +CryptoJS.AES.encrypt(JSON.stringify({user_id: '722e87fa398e4faeb228fe27d6b2a7a6', new_password: '2', confirm_password: '2'}), CryptoJS.enc.Base64.parse("Qqk39LaSxFml8cwZLxFNCk1NXun6i2nXebB2rouRgNA="), {iv: CryptoJS.enc.Base64.parse("F1wyy3xQ42vTbm2JZ+6yow=="), mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}).toString(); +``` + +![image-20251103112508998](images/[GCCCTF 2025]守法公民-6.png) + +把这个发过去: + +fFtkxNNsnuawsN2sm453BBWFPrv5dBY/rNGZBu9w1xJ4G2jta74pxOIFOc607+rMM8D+OIrDcP6i16QCkjHfO2P1UkMc2j0rXJcOAWQ33irprAdmLjAFKdj2M+pfVPaz + +![image-20251103112632563](images/[GCCCTF 2025]守法公民-7.png) + +然后用 admin/2 登录 + +有一个分数设置,设置一个比较高的分数,然后随便答题就可以拿到flag了 + +![image-20251103112755005](images/[GCCCTF 2025]守法公民-8.png) \ No newline at end of file diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-1.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-1.png" new file mode 100644 index 0000000..2439ee6 Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-1.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-2.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-2.png" new file mode 100644 index 0000000..7f344c4 Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-2.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-3.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-3.png" new file mode 100644 index 0000000..7bb39d5 Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-3.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-4.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-4.png" new file mode 100644 index 0000000..22e52c8 Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-4.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-5.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-5.png" new file mode 100644 index 0000000..ad58806 Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-5.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-6.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-6.png" new file mode 100644 index 0000000..4e335fa Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-6.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-7.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-7.png" new file mode 100644 index 0000000..79ffe8e Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-7.png" differ diff --git "a/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-8.png" "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-8.png" new file mode 100644 index 0000000..b1cfc0c Binary files /dev/null and "b/web/images/[GCCCTF 2025]\345\256\210\346\263\225\345\205\254\346\260\221-8.png" differ