本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:AspEncode是一种用于编码ASP脚本的工具,防止未授权查看,适用于需要保护源代码安全的Web开发者。本文介绍了批量解密AspEncode脚本的技术挑战与工具使用方法,包括逆向工程解析编码规则、字符串解码、变量解密、控制流恢复等步骤。批量解密工具能够处理多个ASP文件,提升开发效率,但开发者需确保使用合法性与道德性,避免侵权行为。
AspEncode

1. AspEncode编码工具简介

1.1 工具概述

AspEncode是一款广泛应用于ASP(Active Server Pages)环境下的字符串编码工具。它通过特定的算法将敏感字符串转换成难以阅读的格式,以保护源代码不被轻易理解,常用于防止源代码泄露和防止恶意用户利用代码中的敏感信息。AspEncode在软件安全领域中扮演着基础而重要的角色。

1.2 应用场景

AspEncode的应用场景主要集中在网站与应用程序的源代码保护。开发者使用该工具将如数据库连接字符串、API密钥、认证令牌等敏感信息编码,从而降低代码被他人非法利用的风险。此外,它也可用于代码审查过程中的信息隐藏。

1.3 功能优势

AspEncode提供了多种编码选项,包括但不限于URL编码、Base64编码以及自定义的复杂编码方式。它最大的优势在于使用简单,易于集成到现有的开发流程中,并且能有效提高代码的安全性。尽管如此,AspEncode并非万能,它不能防止代码被完全逆向,因此在某些高安全要求的场景下,可能还需要配合其他安全措施。

通过本章的介绍,我们能够对AspEncode有一个基本的认识,并理解其在代码保护中的作用与重要性。接下来的章节将深入探讨编码与解密的逆向工程原理和实践,以及如何有效地解码和解密使用AspEncode编码的数据。

2. 编码与解密的逆向工程

2.1 编码工具的内部逻辑

2.1.1 AspEncode工作原理分析

AspEncode是一个在ASP(Active Server Pages)环境中广泛使用的编码工具。工作原理是通过特定的算法将源代码转换为不可直接读取的格式,以此来增加代码的安全性和保护知识产权。一般来说,AspEncode会对代码进行两种基本操作:字符替换和控制结构转换。

字符替换将源代码中的关键字符通过查找表或者特定算法进行替换,生成新的字符序列。例如,将一些关键函数名、变量名替换为难以理解的名称。控制结构转换则是将常见的逻辑结构例如if语句、循环等,转换为更复杂、更难以跟踪的形式。

为了深入了解AspEncode的内部逻辑,可以分析其编码后的脚本。在编码后的文件中,可以看到AspEncode通常会使用一些动态执行(例如eval函数)的方式来替代静态的脚本代码。这意味着,即便攻击者能够读取到编码后的脚本,也难以直接理解代码的逻辑。

2.1.2 编码过程中的关键算法

AspEncode的编码过程涉及一系列关键算法,这些算法是确保代码安全性和复杂性的核心。下面是一些在编码过程中可能使用的关键算法:

  • 字符替换算法 :为避免直接使用源代码中的字符,AspEncode可能使用一种映射表来替换字符。例如,原始字符 a 可能在映射表中对应转换成数字 97 。这种替换是多对一的,且映射表在每次编码时都可能改变,从而增强了安全性。
  • 控制结构混淆算法 :AspEncode可能将简单的控制结构如 if 语句转换为更复杂的结构。例如,一个简单的 if 判断可能会被拆分成多个 if 和 else if 的嵌套,或者通过增加额外的计算和条件判断来混淆真正的逻辑。

  • 垃圾代码注入算法 :为了进一步迷惑逆向工程师,AspEncode可能会注入一些无用的代码(通常称为垃圾代码)。这些代码在执行时不会影响程序的运行结果,但会使得逆向分析变得更加困难。

  • 函数和变量的动态调用 :在某些情况下,AspEncode会用字符串拼接或动态生成的方式来调用函数和变量,而不是直接使用它们。这意味着攻击者需要理解整个字符串如何被解析和执行,这增加了攻击的难度。

为了理解这些算法的工作过程,可以查看AspEncode编码后的代码片段。通过逆向工程的手段,尝试恢复原始的控制流程和数据结构,这有助于深入理解编码工具的具体实现细节。

2.2 解密的基本原理

2.2.1 逆向工程的工具与方法

逆向工程是指将软件程序的已编译形式(如二进制文件或字节码)转换为可读的源代码或逻辑结构的过程。在解密领域,逆向工程主要是指理解编码工具的编码逻辑,并尝试还原出原始的可执行代码或数据。

逆向工程的工具包括但不限于:

  • 反汇编器(Disassemblers) :将机器代码反汇编成汇编代码,便于理解程序的底层行为。
  • 反编译器(Decompilers) :尝试将编译后的代码还原为高级语言代码,如C或C++。
  • 调试器(Debuggers) :运行程序并允许操作者逐步检查程序的状态。
  • 脚本分析工具 :分析特定类型的脚本语言,例如Python、JavaScript或ASP脚本。

逆向工程的方法包括:

  • 动态分析(Dynamic Analysis) :在运行时监控程序的行为,使用调试器来跟踪程序执行的流程。
  • 静态分析(Static Analysis) :在不执行程序的情况下分析代码,通常使用反编译器。
  • 人工分析(Manual Analysis) :专家根据自己的经验和知识,对程序的结构和逻辑进行深入分析。

2.2.2 常见的编码与解密技术对比

编码技术与解密技术始终在进行一场“猫鼠游戏”。编码工具试图通过各种手段提高代码的安全性,而解密技术则不断寻找新的方法来破解这些编码。以下是一些常见的编码和解密技术的对比:

  • 简单加密与简单解密 :
  • 简单加密:例如使用简单的字符替换算法。
  • 简单解密:攻击者可以手动或使用工具很容易地进行逆向。

  • 复杂的控制流混淆与反混淆技术 :

  • 复杂混淆:如通过循环展开、条件语句混淆来混淆控制流。
  • 反混淆:逆向工程师利用模式识别、算法分析等技术对控制流进行反混淆。

  • 代码混淆与代码硬化技术 :

  • 代码混淆:通过插入垃圾代码、改变控制结构等方式使逆向工程变得更加困难。
  • 代码硬化:使用更高级的算法来检测和抵御逆向工程尝试,例如检查调试器的存在或使用虚拟化技术。

  • 动态加密与静态解密 :

  • 动态加密:代码在运行时动态解密。
  • 静态解密:尝试在程序启动之前或在未运行状态下分析代码。

要对抗这些编码技术,解密者需要不断更新自己的技能和工具,并跟上编码技术的发展趋势。这在某种程度上,也是技术进化的动力之一。

总结来说,第二章探讨了编码与解密的逆向工程,详细分析了AspEncode编码工具的内部逻辑,包括它的工作原理和关键算法。同时,深入研究了解密的基本原理,从逆向工程的工具和方法,到常见的编码与解密技术对比,为读者提供了一个全面的逆向工程视角。

3. 字符串解码方法

在第三章中,我们将深入探讨字符串解码的各种方法和技术。字符串是编程中常见的数据类型,用于存储文本信息。在加密和编码过程中,字符串的处理尤为关键。我们将按照从基础到高级的顺序,依次介绍静态字符串解码和动态字符串解码的概念和实施策略。

3.1 静态字符串解码

静态字符串解码指的是在编译时或者在程序未运行之前进行的字符串解码。这种解码通常涉及到模式识别、算法匹配等技术。

3.1.1 模式识别与匹配技术

模式识别是指计算机从原始数据中识别出特定信息的过程。在字符串解码中,我们常常利用模式识别来确定字符串的编码方式。例如,如果我们知道目标字符串是以Base64编码的,那么就可以使用特定的正则表达式或者解析库来解码。

import base64

# Base64编码的字符串示例
encoded_str = 'SGVsbG8gV29ybGQh'  # Hello World!

# Base64解码
decoded_bytes = base64.b64decode(encoded_str)
decoded_str = decoded_bytes.decode('utf-8')

print(decoded_str)  # 输出:Hello World!

在这个例子中,我们使用了Python的内置库 base64 来进行编码和解码。首先将编码后的字符串 encoded_str 进行Base64解码,再将解码得到的字节序列转换成字符串 decoded_str 。

3.1.2 动态字符串解码的策略

动态字符串解码是指在程序运行时进行的解码操作。由于编码方式可能在运行时才确定,因此解码过程需要动态地对字符串进行分析和处理。

// C#动态解码字符串示例
string encodedStr = "SGVsbG8gV29ybGQh"; // Base64编码的字符串
byte[] bytes = Convert.FromBase64String(encodedStr);
string decodedStr = Encoding.UTF8.GetString(bytes);

Console.WriteLine(decodedStr); // 输出:Hello World!

在C#代码示例中,我们首先将Base64编码的字符串转换为字节序列,然后使用UTF-8的字符编码将字节序列解码成字符串。这个过程需要在运行时根据字符串的具体内容动态选择合适的解码方法。

3.2 动态字符串解码

动态字符串解码的挑战在于解码时机和解码策略的选择。在这一小节中,我们将着重分析运行时解码的实现方法和安全性问题。

3.2.1 运行时字符串解码的实现

运行时解码通常需要对代码执行流程有更深入的理解。实现运行时解码的关键在于识别编码字符串,并且能够将其实时转换为有意义的数据。

// JavaScript动态解码示例
const encodedStr = "SGVsbG8gV29ybGQh"; // Base64编码的字符串
const decodedStr = Buffer.from(encodedStr, 'base64').toString();

console.log(decodedStr); // 输出:Hello World!

在这个JavaScript代码段中,我们使用了 Buffer 对象来处理Base64编码的字符串。 Buffer.from 方法将编码字符串转换为Buffer对象,然后 toString 方法将该Buffer对象转换为解码后的字符串。

3.2.2 安全性分析与防范措施

在运行时进行字符串解码时,必须考虑安全性问题。如果解码过程中不加防范,则可能会引入安全漏洞,例如注入攻击或者解码错误。

防范措施可以包括:

  1. 验证编码字符串的合法性,确保其符合预期的格式。
  2. 限制解码操作的执行环境,避免在不受信任的上下文中执行。
  3. 对解码后的数据进行验证,确保其符合业务逻辑和安全要求。

例如,在Web应用中,我们可以使用白名单验证输入数据是否为有效的编码字符串,并确保解码操作不会造成XSS攻击。

def safe_decode(encoded_data):
    # 验证编码数据是否有效
    if not isinstance(encoded_data, str) or not encoded_data.startswith('base64:'):
        raise ValueError("Invalid input data")

    # 尝试Base64解码
    try:
        decoded_bytes = base64.b64decode(encoded_data[len('base64:'):])
    except binascii.Error:
        raise ValueError("Decoding error")

    # 对解码数据进行安全检查
    decoded_str = decoded_bytes.decode('utf-8')
    if not is_safe_output(decoded_str):
        raise ValueError("Unsafe decoded content")

    return decoded_str

def is_safe_output(content):
    # 实现安全检查逻辑
    # ...
    return True  # 假设内容是安全的

在Python中,我们定义了一个 safe_decode 函数,它首先验证输入数据是否以 base64: 开头,然后尝试进行Base64解码。此外,我们还对解码后的字符串进行了安全性检查,确保其不会引起安全问题。

表格:字符串解码方法对比

方法 适用场景 优点 缺点
静态解码 编译时解码 简单易实现 无法处理动态生成的字符串
动态解码 运行时解码 灵活应对变化 可能存在安全风险
基于正则表达式 编码模式已知 效率较高 编码规则变化时需要更新正则
使用内置库函数 编码格式标准 稳定可靠 需要预先知道编码格式

通过表格我们可以看到不同字符串解码方法的适用场景、优点和缺点。选择合适的方法需要根据实际情况进行判断。

Mermaid 流程图:动态字符串解码流程

graph LR
A[输入编码字符串] --> B{判断编码格式}
B --> |已知格式| C[使用内置库解码]
B --> |未知或动态格式| D[分析字符串模式]
D --> E[选择适当的解码方法]
E --> F[执行解码操作]
F --> G[输出解码后的字符串]
C --> G

根据上述流程图,我们可以观察到动态字符串解码的步骤包括判断编码格式、分析字符串模式、选择适当的解码方法、执行解码操作,最终输出解码后的字符串。这个流程能够清晰地指导解码操作的实施。

通过本章节的介绍,我们可以看到字符串解码技术的复杂性和多样性。不同的解码方法应对不同的使用场景和挑战。无论是在静态环境中还是在动态环境下,选择正确的解码策略对于成功解密都是非常关键的。同时,考虑到安全性问题,开发者在实现解码功能时必须小心翼翼,避免潜在的安全风险。

4. 变量解密技术

4.1 变量加密的特点

4.1.1 变量加密的常用手段

在软件工程中,变量加密是保护数据在存储或传输过程中不被未授权用户读取或篡改的重要手段。变量加密有多种形式,包括但不限于对称加密、非对称加密、哈希加密等。

对称加密是最为常见的加密手段之一,它使用相同的密钥进行数据的加密和解密。常见的对称加密算法包括AES(高级加密标准)、DES(数据加密标准)和3DES(三重数据加密算法)。对于这些算法,密钥管理是关键,因为它直接影响了加密系统的安全性。

非对称加密,又称为公钥加密,使用一对密钥:公钥和私钥。公钥可以公开,用于加密信息;私钥必须保密,用于解密。RSA算法是这一类型的代表。它在Web安全和数字签名领域中应用广泛。

哈希加密,如SHA(安全散列算法)系列,其特点是单向加密,只能加密而不能解密,因此主要用于验证数据的完整性和唯一性。哈希加密不是为了保护数据的机密性,而是确保数据的原始性。

4.1.2 加密强度评估与破解难度分析

评估加密算法的强度,通常需要考虑以下几个方面:

  • 密钥长度 :更长的密钥提供了更强的加密安全保证,因为穷举密钥的可能性增大了。
  • 算法复杂度 :算法的复杂程度直接关系到破解的难易程度。一些设计精良的算法,如AES,即使公开了算法细节,也很难被破解。
  • 实现的可靠性 :加密技术的安全性很大程度上取决于其在特定环境下的实现。即使算法本身很强大,错误的实现也可能导致漏洞。
  • 攻击模型 :针对不同类型的攻击(如暴力攻击、侧信道攻击等),需要设计不同的保护机制。

破解难度分析是逆向工程中的一个核心话题,破解者可能利用各种技术和方法尝试突破加密。例如,利用已知的明文与密文,对加密算法进行密码分析尝试找到密钥。

破解难度的高低与加密强度成反比。要提升破解难度,可以从增强算法复杂度、使用更长密钥和采用更为安全的实现方式等方面入手。

4.2 变量解密的实践技巧

4.2.1 解密工具的应用

使用解密工具是逆向工程中的常见手段。这些工具可以帮助开发者执行静态分析(不运行程序情况下分析程序)或动态分析(在程序运行时进行分析)。

一些流行的解密工具有IDA Pro、Ghidra、Radare2和Hopper。这些工具能够对编译后的程序进行反汇编,显示程序的汇编代码。在某些情况下,如果开发者有原始的源代码,这些工具还可以提供源码和汇编代码的交叉参考。

当使用解密工具时,开发者需要对逆向工程的基础知识有所了解,比如如何识别加密函数、如何追踪数据流以及如何在不同视图间切换查看程序的执行逻辑。

4.2.2 解密过程中的问题与解决

在变量解密的过程中,开发者可能会遇到各种问题。比如,加密算法可能被设计得非常复杂,或者代码中可能有反调试机制来阻止逆向工程。此外,解密后的数据可能会以非人类可读的格式存储,需要进一步的解析和转换。

为了解决这些问题,开发者可以采用以下策略:

  • 动态调试 :使用调试器(如OllyDbg、x64dbg)在运行时监视程序的行为,寻找加密和解密的关键时刻。
  • 代码混淆识别 :通过识别和理解代码混淆技术,开发者可以尝试找到解密的原始逻辑。
  • 数据追踪与恢复 :追踪加密数据流,使用工具或编写脚本来提取和恢复原始数据。
# 代码示例:使用Python脚本解析和恢复加密的数据
def decrypt_data(encrypted_data, key):
    """
    模拟一个简单的解密函数,将加密的数据和密钥作为输入,输出解密后的数据。
    在实际场景中,解密算法会更加复杂。
    """
    decrypted_data = []
    for i in range(0, len(encrypted_data), key):
        decrypted_data.append(encrypted_data[i] ^ key)
    return ''.join(map(chr, decrypted_data))

encrypted_data = b'\x98\x45\x89\xB6\x7C\x5A\x34\x12'
key = 0x1A  # 假设的密钥
print(decrypt_data(encrypted_data, key))

通过脚本能够将上述过程自动化,但需要注意的是,在反向工程过程中,合法性和道德性原则必须被遵守。未经许可的解密可能违反版权法和计算机犯罪法,开发者必须确保其行为是合法的,并且遵守相关的道德标准。

5. 控制流恢复过程

5.1 控制流分析技术

5.1.1 控制流图的构建方法

控制流图(Control Flow Graph, CFG)是逆向工程中用于表示程序执行流程的图形化模型。构建CFG通常涉及以下步骤:

  1. 识别基本块(Basic Blocks) :
    - 基本块是程序中不可再分的序列,通常以跳转指令开始,以跳转指令结束。
    - 它们是CFG的节点。

  2. 建立节点之间的关系 :
    - 节点之间的边表示控制流的转移,包括跳转、函数调用和返回等。

  3. 使用图数据结构表示 :
    - CFG可以用邻接表或邻接矩阵等图数据结构表示,以便于计算机处理。

在构建CFG的过程中,我们可以通过分析汇编代码或高级语言生成的中间表示来识别基本块,并使用控制流分析算法来确定这些基本块之间如何连接。

5.1.2 控制流恢复的策略与实现

控制流恢复是逆向工程的核心部分,对于理解程序的执行逻辑至关重要。以下是控制流恢复的一些策略:

  • 静态分析 :
  • 利用静态分析工具对二进制代码进行分析,以识别函数、循环和条件分支。
  • 通过算法识别出程序的执行路径和潜在的控制流。

  • 动态跟踪 :

  • 运行程序并在运行时收集控制流信息。
  • 动态分析通常需要一个调试器或特定的跟踪工具。

  • 符号执行 :

  • 这是一种程序分析技术,其中执行路径是通过符号值而不是具体数值来处理的。
  • 符号执行可以探索程序中的所有可能路径。

在实现控制流恢复时,逆向工程师可能会使用各种工具来辅助分析,如IDA Pro、Ghidra、Radare2等。这些工具能够提供可视化的CFG,并允许用户标记和分析不同控制流的特性。

5.2 混淆代码的处理

5.2.1 代码混淆的目的与类型

代码混淆是一种旨在使程序难以理解的代码转换技术。其主要目的是为了防止逆向工程,从而保护软件知识产权和防止未授权的修改或破解。常见的代码混淆类型包括:

  • 控制流混淆 :
  • 修改程序的控制流结构,如添加空循环、无关的分支、条件跳转等。

  • 数据混淆 :

  • 对变量和数据结构进行转换,如使用哈希表代替数组等。

  • 算术混淆 :

  • 对程序中的算术运算进行复杂的转换,如使用等价的数学表达式替换原有运算。

5.2.2 混淆代码的逆向操作步骤

处理混淆代码需要耐心和高级的逆向工程技能。以下是处理混淆代码的一般步骤:

  1. 识别混淆类型 :
    - 分析程序,确定使用的混淆技术类型。

  2. 去混淆技术的应用 :
    - 应用逆向操作来简化或反转混淆的代码。
    - 这可能包括重写某些代码段,以恢复其原始逻辑。

  3. 自动化工具的使用 :
    - 使用去混淆工具,如De4dot或Unpacker,来自动化去混淆过程。

  4. 手动分析 :
    - 在自动工具无法处理的情况下,手动逆向推导原始代码逻辑。
    - 这可能需要对程序的行为进行监控,并通过调试器逐步分析。

  5. 测试与验证 :
    - 对去混淆后的代码进行测试,确保其功能与原始混淆版本相同。

处理混淆代码的逆向工程过程是复杂的,并且可能涉及大量的重复劳动。然而,掌握这些技能对于逆向工程师来说是必不可少的。通过实践和深入分析,逆向工程师可以提升其处理混淆代码的能力,并且能够更好地理解和分析复杂程序的执行流程。

6. 批量解密工具的操作与效率

6.1 批量解密工具的设计原理

批量解密工具的设计原理是提高效率和操作简便性的关键。这些工具通常需要处理大量数据,因此设计时必须考虑到解密流程的高效性。以下是设计高效解密流程的几个要点:

  1. 并行处理能力 :工具应能利用多核处理器的能力,对多个数据文件或数据块同时进行解密操作。
  2. 最小化I/O操作 :减少不必要的硬盘读写操作,可以显著提升解密速度。
  3. 缓存优化 :合理使用内存缓存,避免重复解密相同的数据。
  4. 异步IO操作 :使用异步IO处理可以避免因IO操作阻塞CPU资源,提高CPU利用率。

工具的用户交互与操作界面也是设计时的重要考虑因素。一个好的用户界面可以帮助用户更有效地使用工具,减少错误操作的发生。用户界面应包括:

  1. 直观的用户指南 :提供清晰的使用说明,帮助用户快速上手。
  2. 友好的批量操作界面 :能够简单地添加多个待解密文件,并提供进度指示。
  3. 高级选项设置 :对于高级用户,应提供更复杂的设置,比如算法选择、内存使用量等。

示例代码块:批量解密处理流程

import os
import threading
from decryption_lib import decrypt_file

def decrypt_files_in_parallel(file_list, output_folder):
    def decrypt_file_wrapper(file_path):
        try:
            decrypted_data = decrypt_file(file_path)
            filename = os.path.basename(file_path)
            with open(os.path.join(output_folder, filename), 'wb') as f:
                f.write(decrypted_data)
        except Exception as e:
            print(f"Error decrypting file {file_path}: {e}")

    threads = []
    for file_path in file_list:
        thread = threading.Thread(target=decrypt_file_wrapper, args=(file_path,))
        threads.append(thread)
        thread.start()
    for thread in threads:
        thread.join()

file_list = ['path/to/encrypted/file1', 'path/to/encrypted/file2', ...]
output_folder = 'path/to/decrypted/files'
decrypt_files_in_parallel(file_list, output_folder)

上述代码展示了如何在Python中使用线程并行解密文件的简单示例。每个线程负责解密一个文件,并将解密结果保存到指定的输出文件夹中。

6.2 工具性能优化

在批量解密场景下,性能优化是提高效率和提升用户体验的关键因素。以下是一些提升解密速度的算法优化方法以及处理大量数据时的内存管理策略。

提高解密速度的算法优化

  • 快速算法选择 :在多种解密算法中选择执行速度最快的算法。
  • 硬件加速 :如果条件允许,可以利用GPU加速解密过程。
  • 预计算与缓存 :对于可预测的或重复的计算部分预先计算,并将结果缓存。

大量数据处理时的内存管理

  • 内存池技术 :使用内存池技术来管理内存分配和回收,减少内存碎片。
  • 分批处理 :将大文件分批读入内存处理,避免一次性加载过多数据导致内存溢出。
  • 磁盘交换 :利用虚拟内存机制,当物理内存不足时,将部分数据交换到磁盘。

代码块:内存管理示例

import io

def process_file_in_chunks(file_path, chunk_size=1024):
    with open(file_path, 'rb') as f:
        while True:
            chunk = f.read(chunk_size)
            if not chunk:
                break
            # 进行解密操作
            decrypted_chunk = decrypt_chunk(chunk)
            # 处理解密后的数据
            process_decrypted_chunk(decrypted_chunk)
            del chunk  # 删除已处理的内存块,帮助Python的垃圾回收器回收内存

def decrypt_chunk(chunk):
    # 解密算法实现,省略具体实现细节
    pass

def process_decrypted_chunk(chunk):
    # 数据处理实现,省略具体实现细节
    pass

# 调用文件处理函数
process_file_in_chunks('path/to/large/encrypted/file')

上述代码展示了如何以分块的方式读取和处理大文件,有助于管理内存的使用,防止因一次性读入过大的数据导致内存溢出。

注意:以上代码仅为示例,实际使用时应根据具体的解密算法和应用场景进行适当的修改。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:AspEncode是一种用于编码ASP脚本的工具,防止未授权查看,适用于需要保护源代码安全的Web开发者。本文介绍了批量解密AspEncode脚本的技术挑战与工具使用方法,包括逆向工程解析编码规则、字符串解码、变量解密、控制流恢复等步骤。批量解密工具能够处理多个ASP文件,提升开发效率,但开发者需确保使用合法性与道德性,避免侵权行为。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐