CTF密码学实战:Python 2/3双版本XXTEA解密脚本全解析
1. 项目概述:当CTF遇到XXTEA
在CTF(Capture The Flag)竞赛的密码学赛道上,你经常会遇到一些看似简单、实则暗藏玄机的加密题。它们可能伪装成一段奇怪的字符串,或者隐藏在流量包、图片的元数据里。最近几年,一种名为XXTEA的加密算法在CTF题目中出现的频率越来越高,尤其是在一些考察代码审计和脚本编写能力的Misc(杂项)或Crypto(密码学)题目中。这类题目的典型套路是:给你一个加密后的密文(可能是一串十六进制或Base64编码的文本),再给你一个用Python 2写的、看起来有点“年久失修”的加密脚本,然后让你找出Flag。
为什么是Python 2?因为很多老题目,或者出题人为了增加一点“复古”的障碍,会故意使用旧版本。而XXTEA算法本身,在Python 2和Python 3下的处理细节,尤其是涉及字节(bytes)和长整型(long)时,有着微妙的、足以让解密失败的区别。如果你只会照搬脚本,或者试图用Python 3直接运行一个为Python 2写的XXTEA解密函数,大概率会得到一堆乱码,然后卡在解题的第一步。
这个项目要解决的,就是这样一个非常具体且实战性极强的问题: 如何快速、准确地破解基于XXTEA算法的CTF题目,并提供一个能同时兼容Python 2和Python 3的健壮解密脚本。 这不仅是一个解密工具,更是一份理解算法跨版本差异的实战指南。无论你是刚接触CTF的新手,还是在寻找通用解题模板的老手,这份手把手的教程和双版本脚本都能让你在面对XXTEA时,从“可能能解”变成“一定能解”。
2. XXTEA算法核心原理与CTF中的变形
要破解,先得理解。XXTEA(Corrected Block TEA)是TEA系列加密算法的一种,由David Wheeler和Roger Needham设计。它是一种分组密码,但特别适合在内存或资源受限的环境下进行软件实现,这也是它常被用于CTF题目的原因之一——出题人可以用很少的代码实现一个看起来还不错的加密。
2.1 算法流程精要
XXTEA加密的对象是一个32位无符号整数数组 v ,密钥是另一个32位无符号整数数组 k 。它的核心是一个循环结构,通过多轮迭代和混合,将明文数组 v 与密钥数组 k 充分搅拌。算法的伪代码逻辑清晰:
- 预处理 :将明文按32位(4字节)一组切分,填充或调整成长度为
n的数组v。密钥也处理成4个32位整数的数组k(如果不足4个,通常会循环填充或补零)。 - 加密循环 :这是一个巨大的循环,轮数很多(例如52轮)。在每一轮中,算法会遍历
v数组中的每一个元素,根据其前一个元素、后一个元素、当前轮次、一个固定的“黄金比例”常数delta(0x9E3779B9),以及密钥k,来更新当前元素的值。这个更新操作包含了移位、异或、加法等基本运算。 - 输出 :处理后的
v数组,就是密文对应的整数数组。通常会被转换成十六进制字符串或Base64输出。
解密过程是加密的逆过程,使用相同的密钥 k 和 delta 常数,按照相反的顺序和运算进行还原。
2.2 CTF题目的常见“坑点”
在理想的密码学教材里,算法描述是完美的。但在CTF实战中,出题人会在实现上做手脚,这也是考察的一部分:
- 自定义的
delta常数 :标准XXTEA使用0x9E3779B9,但出题人可能会改成0x9E3779B8或任意其他值。解密时必须使用相同的值。 - 循环轮数(rounds) :标准实现通常用
6 + 52/n(n为v的长度)来确定轮数,但题目可能写死一个固定值,比如32或64。 - 数据填充(Padding) :明文长度不是4字节的整数倍怎么办?常见填充方式有PKCS#7、零填充(
\x00)、或者自定义填充(比如填充到8字节倍数)。解密后需要正确去除填充才能得到Flag。 - 编码混淆 :密文可能不是直接的字节流,而是被编码过。最常见的是Base64编码(
aGVsbG8=这种),也可能是十六进制字符串(68656c6c6f)。解密脚本第一步就需要正确解码。 - 密钥的表示形式 :密钥可能以字符串形式给出(如
“ctf2024”),需要你将其转换为整数数组。转换方式(字节序)可能也是考点。
注意 :很多网上找到的XXTEA示例代码是“正确”的,但直接套用到CTF题上会失败,原因就是上述实现细节不匹配。我们的脚本必须足够灵活,能适配这些变形。
2.3 Python 2 vs Python 3:关键差异解析
这是本项目的重中之重。同一个XXTEA算法逻辑,在Python 2和3上跑出不同结果,主要根源于两点:
-
整数类型与溢出处理 :
- Python 2 :有
int和long两种类型。普通整数运算溢出后会自动提升为long(无限精度)。在XXTEA的位运算中,这有时会导致中间结果变成一个非常大的long整数,虽然不影响最终逻辑,但可能影响后续的字节转换。 - Python 3 :只有
int,且是无限精度的。没有溢出概念。这听起来更好,但问题在于,XXTEA算法本质上是模拟32位无符号整数的模2^32加法。在Python 3中,我们需要 显式地 对0xFFFFFFFF进行按位与(&)操作,来模拟32位溢出截断。Python 2中由于int会溢出成long,有时反而“忘记”了这个限制,导致一些“错误”的实现在Python 2上能跑通,在Python 3上却不行。
- Python 2 :有
-
字节(bytes)与字符串(str) :
- Python 2 :
str就是字节串。“hello”的类型是str,其本质是字节。这非常直接,但容易混淆文本和二进制数据。 - Python 3 :严格区分
str(Unicode文本)和bytes(二进制数据)。“hello”是str,b“hello”才是bytes。加密解密操作的对象必须是bytes。因此,所有从文件读取、从网络接收的“字符串”,在参与运算前,都必须明确其编码(如.encode(‘utf-8’))或使用bytes类型。
- Python 2 :
一个致命示例 :假设密钥是字符串 “key” 。
- 在Python 2的脚本里,可能直接
struct.unpack(‘<IIII’, key.ljust(16, ‘\x00’))。这里key是str,ljust填充‘\x00’没问题。 - 在Python 3里,
key是str,ljust填充的‘\x00’也是str,struct.unpack需要bytes。直接运行会报错:argument for ‘s’ must be a bytes object。必须先将key转为bytes:key.encode().ljust(16, b‘\x00’)。
我们的双版本脚本,核心任务之一就是优雅地处理这些差异,让同一份代码逻辑在两种环境下都能正确工作。
3. 双版本兼容解密脚本的架构设计
我们的目标不是写两个独立的脚本,而是写一个能智能(或通过简单配置)适应Python 2和Python 3环境的脚本。这里提供两种设计思路,并推荐第二种。
3.1 思路一:运行时环境检测与分支
在脚本开头检测Python版本,然后定义两套不同的辅助函数(如字节转换、整数处理)。这种方法直白,但会导致代码中存在大量 if PY2: … else: … 的语句,逻辑分散,不易维护。
import sys
PY2 = sys.version_info[0] == 2
if PY2:
def to_bytes(s):
return s
def to_str(b):
return b
else:
def to_bytes(s):
return s.encode(‘utf-8’) if isinstance(s, str) else s
def to_str(b):
return b.decode(‘utf-8’) if isinstance(b, bytes) else b
3.2 思路二:统一接口与底层适配(推荐)
我们采用更优雅的方式: 定义一组统一的、版本无关的接口函数 。这些函数内部处理版本差异,对外提供一致的行为。核心的XXTEA加密解密函数只调用这些接口函数,从而保持核心逻辑的纯净。
我们将创建以下几个关键适配函数:
_to_bytes(data):无论输入是Py2的str、Py3的str还是bytes,都统一输出bytes(在Py2下是str)。_str_to_uint32_list(data):将字节串按小端序(<I)打包成32位整数列表。这里内部处理struct.unpack的参数类型差异。_uint32_list_to_str(v):将32位整数列表转换回字节串。内部处理struct.pack的返回类型。_fix_uint32(num):确保一个整数在32位无符号范围内(0 <= num < 2^32)。在Python 3中通过& 0xFFFFFFFF实现;在Python 2中,如果num是long且超出int范围,也需要进行同样的掩码操作以保证一致性。
这样,我们的 xxtea_decrypt 函数将完全基于这些适配函数编写,核心算法部分几乎看不到版本判断的代码,可读性和可维护性大大增强。
4. 手把手实现:Python 2/3双版本XXTEA解密脚本
下面,我们直接上干货,逐行解析这个兼容性极强的解密脚本。你可以将它保存为 xxtea_solver.py ,它将成为你的CTF密码学工具箱里的常客。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
XXTEA 解密脚本 (兼容 Python 2 & 3)
适用于CTF中常见的XXTEA变种加密题目。
作者:你的名字
用法:python xxtea_solver.py --cipher <密文> --key <密钥> [--delta <delta>] [--rounds <轮数>]
"""
import sys
import struct
import base64
# --- 版本兼容性适配层 ---
PY2 = sys.version_info[0] == 2
def _to_bytes(data):
"""将输入统一转换为字节串(Python 2的str, Python 3的bytes)。"""
if data is None:
return b‘’
if PY2:
if isinstance(data, unicode):
return data.encode(‘utf-8’)
# Python 2的str就是bytes
return data
else:
# Python 3
if isinstance(data, str):
return data.encode(‘utf-8’)
elif isinstance(data, bytes):
return data
else:
# 尝试转换为字符串再编码
return str(data).encode(‘utf-8’)
def _str_to_uint32_list(data):
"""将字节串按小端序转换为32位无符号整数列表。"""
data = _to_bytes(data)
# 填充到4字节的倍数
pad_len = (4 - len(data) % 4) % 4
data += b‘\x00’ * pad_len # 使用零填充,这是CTF常见做法
# struct.unpack 需要bytes输入,_to_bytes已保证
count = len(data) // 4
# ‘<I‘ 表示小端序,无符号32位整数
fmt = ‘<%dI’ % count
try:
return list(struct.unpack(fmt, data))
except struct.error as e:
# 处理可能的对齐问题
raise ValueError(“数据长度或格式错误,无法转换为整数列表: {}”.format(e))
def _uint32_list_to_str(v):
"""将32位无符号整数列表转换回字节串。"""
fmt = ‘<%dI’ % len(v)
data = struct.pack(fmt, *v)
# 返回bytes (Py3) 或 str (Py2)
return data
def _fix_uint32(num):
"""确保数字在32位无符号整数范围内 (0 <= num < 2^32)。"""
return num & 0xFFFFFFFF
# --- 核心XXTEA算法实现 ---
def xxtea_decrypt(cipher, key, delta=0x9E3779B9, rounds=None):
"""
XXTEA 解密函数。
Args:
cipher: 密文,可以是字节串、Base64字符串或十六进制字符串。
key: 密钥,字符串或字节串。
delta: 算法常数,默认为0x9E3779B9。
rounds: 加密轮数,默认为None(使用标准公式计算)。
Returns:
解密后的字节串。
"""
# 1. 统一输入为字节串,并尝试自动检测编码
cipher_bytes = _to_bytes(cipher)
# 尝试Base64解码
try:
# 移除可能的换行和空格
cipher_b64 = cipher_bytes.replace(b‘\n’, b‘’).replace(b‘ ’, b‘’)
if len(cipher_b64) % 4 == 0:
cipher_bytes = base64.b64decode(cipher_b64, validate=True)
# print(“[Info] 检测到Base64编码,已解码。”)
except (Exception, TypeError):
pass # 不是Base64,继续尝试十六进制
# 尝试十六进制解码
try:
hex_str = cipher_bytes
if PY2:
# Python 2: bytes就是str,直接判断字符
if all(c in ‘0123456789abcdefABCDEF‘ for c in hex_str):
cipher_bytes = hex_str.decode(‘hex’)
else:
# Python 3: bytes需要解码为str判断
if all(c in ‘0123456789abcdefABCDEF‘ for c in hex_str.decode(‘ascii’, errors=‘ignore’)):
cipher_bytes = bytes.fromhex(hex_str.decode(‘ascii’))
except (Exception, TypeError, ValueError):
pass # 不是十六进制,假定为原始字节
# 2. 将密文和密钥转换为整数列表
v = _str_to_uint32_list(cipher_bytes)
n = len(v)
# 处理密钥:转换为4个32位整数的列表
key_bytes = _to_bytes(key)
# 将密钥填充或截断为16字节(4个整数)
key_filled = (key_bytes * (16 // len(key_bytes) + 1))[:16] if key_bytes else b‘\x00’*16
k = _str_to_uint32_list(key_filled)[:4] # 只取前4个作为密钥
while len(k) < 4:
k.append(0) # 确保密钥数组长度为4
# 3. 确定轮数
if rounds is None:
rounds = 6 + 52 // n # 标准XXTEA轮数计算公式
# 4. XXTEA 解密核心算法
sum = _fix_uint32(delta * rounds)
y = v[0]
for _ in range(rounds):
e = (sum >> 2) & 3
for p in range(n-1, 0, -1):
z = v[p-1]
v[p] = _fix_uint32((v[p] - ((((z>>5)^(y<<2)) + ((y>>3)^(z<<4))) ^ ((sum^y) + (k[(p&3)^e]^z)))) & 0xFFFFFFFF)
y = v[p]
z = v[n-1]
v[0] = _fix_uint32((v[0] - ((((z>>5)^(y<<2)) + ((y>>3)^(z<<4))) ^ ((sum^y) + (k[(0&3)^e]^z)))) & 0xFFFFFFFF)
y = v[0]
sum = _fix_uint32(sum - delta)
# 5. 将整数列表转换回字节串,并尝试去除填充
plain_bytes = _uint32_list_to_str(v)
# 尝试去除零填充:找到最后一个非零字节的位置
# 注意:这里假设是零填充,实际题目可能是其他填充,需要调整
end_pos = len(plain_bytes)
while end_pos > 0 and plain_bytes[end_pos-1] == 0:
end_pos -= 1
plain_bytes = plain_bytes[:end_pos]
return plain_bytes
# --- 主函数与命令行接口 ---
def main():
import argparse
parser = argparse.ArgumentParser(description=‘XXTEA解密工具 (Python 2/3 兼容)’)
parser.add_argument(‘-c’, ‘--cipher’, required=True, help=‘密文 (支持原始字节、Base64、Hex)’)
parser.add_argument(‘-k’, ‘--key’, required=True, help=‘密钥字符串’)
parser.add_argument(‘-d’, ‘--delta’, type=lambda x: int(x, 0), default=0x9E3779B9,
help=‘XXTEA delta常数 (十六进制如0x9E3779B9, 十进制如-1640531527), 默认0x9E3779B9’)
parser.add_argument(‘-r’, ‘--rounds’, type=int, default=None, help=‘加密轮数,默认自动计算’)
parser.add_argument(‘-o’, ‘--output’, help=‘将解密结果输出到文件’)
args = parser.parse_args()
try:
result = xxtea_decrypt(args.cipher, args.key, delta=args.delta, rounds=args.rounds)
# 尝试以UTF-8解码输出,如果不是文本则显示十六进制
try:
output_str = result.decode(‘utf-8’, errors=‘strict’)
print(“[+] 解密成功 (UTF-8文本):”)
print(output_str)
# 检查是否有常见的Flag格式
if ‘flag{’ in output_str.lower() or ‘ctf{’ in output_str.lower():
print(“\n[!] 检测到可能的Flag格式!”)
except UnicodeDecodeError:
print(“[+] 解密成功 (原始字节, 显示为Hex):”)
hex_str = result.hex() if hasattr(result, ‘hex’) else result.encode(‘hex’)
print(hex_str)
print(“\n[!] 输出不是有效UTF-8文本, 请检查是否为二进制数据或使用其他编码。”)
if args.output:
with open(args.output, ‘wb’) as f:
# 确保以二进制写入
f.write(result if isinstance(result, bytes) else result.encode(‘utf-8’))
print(“[+] 结果已写入文件: {}”.format(args.output))
except Exception as e:
print(“[-] 解密失败: {}”.format(e), file=sys.stderr)
sys.exit(1)
if __name__ == ‘__main__’:
main()
4.1 脚本关键点解析
- 自动编码检测 (
xxtea_decrypt函数开头):这是脚本的“智能”所在。它首先尝试Base64解码,失败后再尝试十六进制解码,都失败则视为原始字节。这覆盖了CTF题目中90%的密文给出形式。 - 密钥处理 :将任意长度的密钥字符串通过重复填充到16字节,再取前4个整数。这种方式兼容了短密钥的情况。如果题目密钥就是4个整数,你需要稍微修改这部分逻辑。
-
_fix_uint32的绝对必要性 :在解密循环的每一步加减运算后,都立即使用_fix_uint32(即& 0xFFFFFFFF)来确保数值范围。这是保证Python 3下结果正确的 生命线 。即使在Python 2下,这样做也能保证结果的一致性,避免因隐式长整数带来的潜在问题。 - 填充去除逻辑 :脚本默认使用零填充(
\x00)并在解密后去除末尾的零。 这是最大的一个假设 。如果题目使用PKCS#7填充,你需要修改去除逻辑。一个更稳健的方法是,解密后检查末尾字节的值pad_byte,如果1 <= pad_byte <= 16,且末尾pad_byte个字节都等于pad_byte,则按PKCS#7去除。我们在脚本中注释了这一点,你可以根据题目提示修改。 - 输出尝试 :脚本会先尝试将解密结果当作UTF-8文本解码并打印,因为Flag通常是可读文本。如果失败,则显示十六进制,方便你进一步分析(可能是图片、压缩包等其他数据)。
5. 实战演练:破解一道虚构的CTF题目
让我们假设一道名为 “Easy_XXTEA” 的题目。题目描述:“我们用一个简单的算法加密了flag,你能解开吗?” 附件提供了一个 encrypt.py 和一个 flag.enc 文件。
encrypt.py (Python 2 编写):
import struct
import base64
def xxtea_encrypt(v, k):
n = len(v)
delta = 0x9E3779B9
rounds = 6 + 52//n
sum = 0
y = v[0]
for _ in range(rounds):
sum = (sum + delta) & 0xFFFFFFFF
e = (sum >> 2) & 3
for p in range(n-1):
z = v[p+1]
v[p] = (v[p] + ((((z>>5)^(y<<2)) + ((y>>3)^(z<<4))) ^ ((sum^y) + (k[(p&3)^e]^z)))) & 0xFFFFFFFF
y = v[p]
z = v[0]
v[n-1] = (v[n-1] + ((((z>>5)^(y<<2)) + ((y>>3)^(z<<4))) ^ ((sum^y) + (k[((n-1)&3)^e]^z)))) & 0xFFFFFFFF
y = v[n-1]
return v
key = “ThisIsMySecretKey”
plaintext = “flag{This_Is_A_Test_Flag}”
# 将明文转换为整数列表
v = []
for i in range(0, len(plaintext), 4):
block = plaintext[i:i+4].ljust(4, ‘\x00’)
v.append(struct.unpack(‘<I’, block)[0])
# 将密钥转换为整数列表
k = []
key_padded = key.ljust(16, ‘\x00’)
for i in range(0, 16, 4):
k.append(struct.unpack(‘<I’, key_padded[i:i+4])[0])
cipher = xxtea_encrypt(v, k)
# 将整数列表转换为字节并Base64输出
cipher_bytes = b‘’.join(struct.pack(‘<I’, x) for x in cipher)
cipher_b64 = base64.b64encode(cipher_bytes)
print(cipher_b64)
# 输出类似: uHm1LrAJtK6wCbSusAm0rrAJtK6wCbSusAm0rg==
flag.enc 文件内容: uHm1LrAJtK6wCbSusAm0rrAJtK6wCbSusAm0rg==
我们的解题步骤:
-
分析加密脚本 :快速浏览
encrypt.py。我们发现:- 算法是标准XXTEA。
delta=0x9E3779B9,轮数自动计算。- 密钥是字符串
“ThisIsMySecretKey”。 - 明文填充是
ljust(4, ‘\x00’),即零填充。 - 输出是整数列表转字节后的Base64编码。
-
使用我们的脚本解密 :
python xxtea_solver.py --cipher “uHm1LrAJtK6wCbSusAm0rrAJtK6wCbSusAm0rg==” --key “ThisIsMySecretKey”或者,如果密文保存在文件里:
python xxtea_solver.py --cipher “$(cat flag.enc)” --key “ThisIsMySecretKey” -
获取结果 :脚本会自动检测到Base64编码并进行解码。解密后,它会尝试去除零填充。最终输出应该能看到:
[+] 解密成功 (UTF-8文本): flag{This_Is_A_Test_Flag} [!] 检测到可能的Flag格式!成功拿到Flag。
实操心得 :在实际比赛中,出题人可能会省略加密脚本,只给密文和一句提示“密钥是当天日期”。这时,你需要结合上下文(题目名、附件名、题目描述中的暗示)来猜测密钥。我们的脚本支持通过
--delta和--rounds参数调整算法参数,方便进行爆破或测试。例如,如果怀疑delta被修改,可以写一个循环进行尝试。
6. 常见问题排查与进阶技巧
即使有了通用脚本,实战中还是会遇到各种问题。下面是一个排查清单和进阶技巧。
6.1 解密失败排查表
| 现象 | 可能原因 | 解决方案 |
|---|---|---|
| 输出乱码,但长度似乎正确 | 1. 密钥错误。 2. delta 常数不对。 3. 字节序错误(大端序 vs 小端序)。 |
1. 仔细检查密钥,包括大小写、特殊字符。 2. 尝试常见的 delta 变种 ( 0x9E3779B8 , 0x9E3779BA )。 3. 尝试修改 _str_to_uint32_list 和 _uint32_list_to_str 中的格式字符,将 ‘<I’ (小端序)改为 ‘>I’ (大端序)。 |
| 解密出的字节串末尾有规律的填充字符 | 填充方式不匹配。 | 修改解密函数末尾的去除填充逻辑。如果是PKCS#7,替换为零填充的去除代码。 |
脚本报错 struct.error: unpack requires a buffer of 4 bytes |
密文长度不是4的倍数,且在转换为整数列表时未正确处理填充。 | 确保 _str_to_uint32_list 函数中的填充逻辑被正确执行。我们的脚本已包含零填充。 |
Python 3下运行报错 ‘str’ does not support the buffer interface |
密钥或密文在Python 3下是 str 类型,没有转换为 bytes 。 |
确保所有输入都通过 _to_bytes() 函数处理。我们的脚本已内置此处理。 |
| 解密结果开头几个字符正确,后面乱码 | 加密轮数 ( rounds ) 不匹配。 |
使用 --rounds 参数手动指定轮数。尝试常见值如 32, 64, 或根据 n (数据块数) 计算 6 + 52//n 。 |
| 解密结果完全是不可读的二进制数据 | 可能解密成功了,但Flag被进一步处理(如压缩、二次加密、藏在图片里)。 | 将解密结果 ( -o 输出到文件) 用 file 命令检查文件类型,或用十六进制编辑器查看文件头 ( PK 是ZIP, PNG 是图片等)。 |
6.2 进阶技巧:应对未知参数
-
暴力破解密钥 :如果密钥空间不大(如4位数字PIN),可以写一个简单的循环调用我们的解密函数。注意控制速度,避免卡死。
import itertools cipher = “...” # 你的密文 for pin in itertools.product(‘0123456789’, repeat=4): key = ‘’.join(pin) try: result = xxtea_decrypt(cipher, key) if b‘flag{’ in result: print(“Found key:”, key) print(“Flag:”, result) break except: pass -
识别算法特征 :有时题目不会告诉你这是XXTEA。如何识别?
- 查找常数 :在加密脚本或二进制文件中搜索
0x9E3779B9(或其变体)。这是TEA家族算法的显著标志。 - 观察操作 :代码中有大量的
<<,>>,^,+,& 0xFFFFFFFF,并且有一个大的循环结构,循环内嵌套另一个遍历数组的循环。 - 输入输出 :输入输出通常是32位整数数组或它们的字节表示。
- 查找常数 :在加密脚本或二进制文件中搜索
-
处理非标准填充 :如果题目使用自定义填充,你需要在解密后手动处理。例如,有些题目填充
‘#’字符。在得到plain_bytes后,使用plain_bytes.rstrip(b‘#’)即可。 -
整合到自动化工具链 :你可以将这个脚本函数集成到你的CTF解题框架中。为其编写一个IDA Pro的插件或Ghidra脚本,当识别出XXTEA算法时,自动提取参数并调用此解密函数。
这个脚本的价值在于其 健壮性 和 教育意义 。它不仅仅是一个黑箱工具,通过阅读其代码,你能深刻理解XXTEA在跨Python版本环境下的陷阱所在。下次再遇到类似的题目,你完全可以现场快速分析、修改、调试,而不是漫无目的地搜索。这才是从“解题”到“掌握”的关键一步。
更多推荐



所有评论(0)