CISCN 2025 逆向题 vvvmmm 深度技术解析
好的,我现在需要帮用户总结一篇文章的内容,控制在100个字以内。用户给的文章是关于CISCN 2025逆向题vvvmmm的技术解析。首先,我得快速浏览文章,抓住主要信息。 文章主要讲的是这道题的解题过程,包括UPX脱壳、Unicorn虚拟机保护、RISC-V指令集分析以及加密算法的逆向。作者详细描述了如何识别和去除UPX壳,然后通过分析字符串发现Unicorn Engine的存在,进而确认使用了RISC-V架构。 接下来,作者提取了种子字符串和RISC-V代码,并通过反汇编工具分析了这些代码的逻辑。最终编写了解密脚本,成功解出了48字节的输入字符串,并验证了flag的正确性。 总结一下,文章涵盖了多层保护技术、虚拟机框架、指令集架构以及加密算法的逆向过程。因此,在总结时需要涵盖这些关键点:UPX脱壳、Unicorn虚拟机、RISC-V分析、加密算法逆向以及最终的解密结果。 现在,我需要把这些信息浓缩到100字以内,确保语言简洁明了。可能的结构是:介绍题目类型和保护技术,然后说明解题步骤和结果。 最终总结应包括:题目类型(逆向工程)、使用的保护技术(UPX、Unicorn、RISC-V)、解题步骤(脱壳、提取数据、分析算法)、结果(获得48字节输入和flag)。 这样就能在有限字数内全面覆盖文章的核心内容。 </think> CISCN 2025逆向题vvvmmm是一道结合UPX加壳、Unicorn虚拟机和RISC-V指令集的综合性逆向工程题目。程序要求输入48字节字符串以验证并输出flag。解题过程包括UPX脱壳、提取种子字符串和RISC-V代码、分析DJB31哈希和自定义伪随机数生成器,并通过XOR流加密实现解密。最终获得正确输入并验证成功。 2026-1-9 07:40:46 Author: www.freebuf.com(查看原文) 阅读量:20 收藏

CISCN 2025 逆向题 vvvmmm 深度技术解析

题目概述

vvvmmm是CISCN 2025的一道综合性逆向工程题目,融合了多种现代保护技术。题目要求输入48字节的正确字符串,程序验证成功后会输出flag。题目的核心特点包括:

  • UPX加壳保护

  • Unicorn Engine虚拟机架构

  • RISC-V指令集加密逻辑

  • 多层算法嵌套

本文将从零开始,逐步剖析这道题目的每个技术细节,带领读者理解完整的解题思路。

初步分析与信息收集

程序行为观察

首先运行程序观察其基本行为:

./vvvmmm

程序会显示ASCII艺术图案,然后提示:

input %48c>

这个格式说明符非常关键。%48c表示程序会读取固定的48个字符,而不是常见的以null结尾的字符串。这意味着我们需要提供恰好48字节的输入。

如果输入错误,程序返回"Try again~";如果输入正确,程序输出"Good."并显示flag。

文件特征分析

使用file命令查看文件信息:

file vvvmmm

输出显示:

vvvmmm: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux),
for GNU/Linux 3.2.0, statically linked, no section header

关键信息点:

  1. 静态链接 - 所有库函数都被编译进二进制文件

  2. 无节头表 - 典型的加壳特征

使用strings命令搜索特征字符串:

strings vvvmmm | grep -i upx

发现明确的UPX标识:

UPX!
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 5.02 Copyright (C) 1996-2025 the UPX Team. All Rights Reserved. $

确认程序使用UPX 5.02版本进行了压缩保护。

UPX脱壳技术

UPX加壳原理

UPX(Ultimate Packer for eXecutables)是一个广泛使用的可执行文件压缩工具。其工作原理如下:

  1. 将原始程序的代码段和数据段进行压缩

  2. 在程序入口添加解压缩stub代码

  3. 程序运行时,stub代码先将压缩数据解压到内存

  4. 解压完成后跳转到原始入口点(OEP)执行

UPX的优势在于可以显著减小文件体积,但同时也增加了静态分析的难度。

脱壳操作

UPX提供了原生的脱壳支持:

upx -d vvvmmm -o vvvmmm_unpacked

输出结果:

Ultimate Packer for eXecutables
File size         Ratio      Format      Name
2694441 <- 1133076   42.05%   linux/amd64   vvvmmm_unpacked
Unpacked 1 file.

成功脱壳。原始程序2.69MB被压缩到1.13MB,压缩比约42%。

脱壳验证

脱壳后的文件应该恢复了正常的ELF结构:

file vvvmmm_unpacked

现在可以看到完整的节头表信息,说明脱壳成功。

虚拟机保护识别

搜索虚拟机特征

对脱壳后的程序进行字符串分析:

strings vvvmmm_unpacked | grep -i "qemu\|unicorn" | head -20

发现大量QEMU和Unicorn相关字符串:

qemu: %s: %s
qemu_ld_i32
qemu_st_i32
qemu_ld_i64
qemu_st_i64
WARNING: Your register accessing on id %u is deprecated...
qemu/util/qht.c
qemu/fpu/softfloat.c
qemu/tcg/tcg.c

这明确表明程序使用了Unicorn Engine。

Unicorn Engine技术背景

Unicorn是基于QEMU的轻量级CPU模拟框架,具有以下特点:

  1. 支持多种架构:ARM, ARM64, MIPS, SPARC, X86, RISC-V等

  2. 可以嵌入应用程序中模拟执行其他架构的代码

  3. 提供完整的内存管理和寄存器访问API

  4. 常用于恶意软件分析、fuzzing和安全研究

在CTF逆向题中,Unicorn常被用于实现虚拟机保护:

  • 将核心算法编译为非本地架构的指令

  • 在运行时通过虚拟机模拟执行

  • 增加静态分析和反汇编的难度

确定目标架构

继续搜索架构相关字符串:

strings vvvmmm_unpacked | grep -i "uc_.*reg"

发现大量"uc_riscv"前缀的符号,确认使用RISC-V架构。

关键数据提取

程序运行需要两类关键数据:

  1. 种子字符串(seed) - 用于初始化算法

  2. RISC-V字节码 - 包含验证逻辑

静态分析方法

在专业逆向工具(如IDA Pro或Ghidra)中:

  1. 定位Unicorn初始化代码

    • 搜索"uc_open"、"uc_mem_map"等API调用

    • 这些函数负责初始化虚拟机环境

  2. 定位代码加载点

    • 查找"uc_mem_write"调用

    • 第二个参数指向RISC-V代码的内存地址

    • 第三个参数是代码长度

  3. 提取RISC-V字节码

    • 在IDA中跳转到代码地址

    • 导出对应长度的字节序列

    • 本题中代码长度为0x296字节(662字节)

  4. 查找种子字符串

    • 在数据段搜索32字节的可打印字符序列

    • 或通过交叉引用分析定位字符串常量

动态分析方法

使用GDB在运行时提取数据:

# 设置断点在Unicorn初始化之后
gdb ./vvvmmm
(gdb) catch syscall read
(gdb) run
(gdb) continue

# 搜索内存中的字符串模式
(gdb) find /b 0x400000, 0x800000, 'e', '4', 'Y', '8'

通过内存扫描,可以在特定地址(0x64c6c0)找到32字节的种子字符串。

提取结果

最终获得两个关键数据:

种子字符串(32字节):

e4Y8YRXVzg2HRrCUy35CM0Txq91HzMGZ

这是一个由大小写字母和数字组成的随机字符串,用作密钥派生的初始种子。

RISC-V代码:
长度为662字节,包含完整的验证算法实现。需要使用RISC-V反汇编工具进一步分析。

RISC-V代码逆向分析

RISC-V架构简介

RISC-V是一个开源的指令集架构(ISA),具有以下特点:

  1. 基于精简指令集(RISC)设计哲学

  2. 模块化设计,支持基础指令集+扩展

  3. 支持32位、64位和128位变体

  4. 完全开源,无需授权费用

RISC-V的寄存器命名规则:

  • x0: 硬件零寄存器(始终为0)

  • x1-x31: 通用寄存器

  • 也可使用ABI名称:a0-a7(参数),t0-t6(临时)等

反汇编工具

使用支持RISC-V的反汇编工具:

  • riscv64-unknown-elf-objdump

  • Ghidra(内置RISC-V支持)

  • IDA Pro(需要RISC-V插件)

算法模块分析

通过反汇编分析,RISC-V代码实现了三个核心模块:

模块一: DJB31哈希算法

第一段代码对种子字符串进行哈希计算。

算法逻辑

uint64_t h = 1;
for (int i = 0; seed[i] != 0; i++) {
    h = 31 * h + seed[i];
}

算法特点

  1. DJB31算法由Daniel J. Bernstein设计

  2. 使用魔数31作为乘法因子

    • 31 = 2^5 - 1,可优化为移位和减法

    • 乘以31等价于:(h << 5) - h

  3. 在64位整数范围运算,溢出自然截断

  4. 初始值为1(标准DJB算法通常用5381)

Python实现

MASK64 = (1 << 64) - 1

def djb31_hash(seed: bytes) -> int:
    h = 1
    for b in seed:
        if b == 0:
            break
        h = (31 * h + b) & MASK64
    return h

这个哈希值将作为后续伪随机数生成器的初始状态。

模块二: 伪随机密钥流生成

第二段代码实现了一个定制的伪随机数生成器,用于生成加密密钥流。

核心参数

MOD = 0x13579bdf  # 魔数模数

这个模数是一个精心选择的质数,用于模运算以保证输出的伪随机性。

关键RISC-V指令

mulhu - 高位无符号乘法

mulhu rd, rs1, rs2

功能:计算rs1 * rs2的128位乘积,取高64位存入rd

在64位架构中:

product = rs1 * rs2  # 128位结果
rd = product[127:64]  # 取高64位

remuw - 32位无符号取模

remuw rd, rs1, rs2

功能:计算(rs1[31:0] mod rs2[31:0]),结果符号扩展到64位

特点:

  • 只使用低32位参与运算

  • 结果会进行符号扩展填充高32位

算法实现

def step(a2: int, a4: int) -> tuple[int, int]:
    # 第一组32位模运算
    a2 = remuw(a2, MOD)
    a3 = remuw(a4, MOD)
    a4 = remuw(a2, MOD)
    a5 = remuw(a3, MOD)

    # 左移32位准备高位乘法
    a2_shift = (a2 << 32) & MASK64
    a4_shift = (a4 << 32) & MASK64

    # 高位乘法并取模
    a4 = remu(mulhu(a4_shift, a2_shift), MOD)

    a3_shift = (a3 << 32) & MASK64
    a5_shift = (a5 << 32) & MASK64

    a5 = remu(mulhu(a5_shift, a3_shift), MOD)

    # 恢复原始值
    a2 = (a2_shift >> 32) & MASK64
    a3 = (a3_shift >> 32) & MASK64

    # 迭代混淆10次
    for _ in range(10):
        a4 = remu((a4 * a2) & MASK64, MOD)
        a5 = remu((a5 * a3) & MASK64, MOD)

    # 最终密钥流输出
    a2 = remu((a4 * a2) & MASK64, MOD)
    a4 = remu((a5 * a3) & MASK64, MOD)

    return a2, a4

状态初始化

h = djb31_hash(seed)
a2 = h >> 16  # 取高位
a4 = h        # 取全值

密钥流生成

通过6轮迭代,每轮产生2个32位值,总共生成12个密钥流值:

keystream = []
for i in range(6):
    a2, a4 = step(a2, a4)
    keystream.append(a2 & 0xffffffff)
    keystream.append(a4 & 0xffffffff)

辅助函数实现

def sext32(x: int) -> int:
    """32位符号扩展到64位"""
    x &= 0xffffffff
    if x & 0x80000000:
        return x | 0xffffffff00000000
    return x

def remuw(dividend: int, divisor: int) -> int:
    """32位无符号取模,结果符号扩展"""
    res = (dividend & 0xffffffff) % (divisor & 0xffffffff)
    return sext32(res)

def mulhu(x: int, y: int) -> int:
    """64位无符号乘法,取高64位"""
    return ((x & MASK64) * (y & MASK64) >> 64) & MASK64

def remu(x: int, d: int) -> int:
    """64位无符号取模"""
    return (x & MASK64) % (d & MASK64)

模块三: XOR流加密验证

第三段代码将用户输入与密钥流进行XOR运算,然后与预设目标值比较。

加密方案

这是一个标准的流加密(Stream Cipher)方案:

  1. 明文处理:将48字节输入解析为12个32位整数(小端序)

  2. 密钥流生成:使用前述算法生成12个32位密钥

  3. 加密操作:ciphertext = plaintext XOR keystream

  4. 验证:比较加密结果与预设目标值

目标常量

target = [
    0x45034f63, 0x534762d2, 0x44b36d04, 0x44c3ed6a,
    0x79bb60b0, 0x42a1e767, 0x3edb7e6c, 0x30e1551d,
    0x4d3abaa4, 0x6aa29948, 0x51ce8847, 0x51623faf
]

这12个32位常量嵌入在RISC-V代码中,通过反汇编提取。

验证逻辑

# 将输入转换为32位整数数组
input_words = []
for i in range(0, 48, 4):
    word = int.from_bytes(input[i:i+4], 'little')
    input_words.append(word)

# 生成密钥流并加密
output = []
a2, a4 = initial_state()
for i in range(6):
    a2, a4 = step(a2, a4)
    ks0 = a2 & 0xffffffff
    ks1 = a4 & 0xffffffff

    output.append(input_words[2*i] ^ ks0)
    output.append(input_words[2*i+1] ^ ks1)

# 验证
if output == target:
    print("Good.")
else:
    print("Try again~")

XOR运算的密码学特性

XOR运算具有以下重要性质:

  1. 自反性: A XOR B XOR B = A

  2. 交换律: A XOR B = B XOR A

  3. 结合律: (A XOR B) XOR C = A XOR (B XOR C)

基于自反性,我们可以轻松解密:

plaintext XOR keystream = ciphertext
ciphertext XOR keystream = plaintext

因此:

plaintext = ciphertext XOR keystream = target XOR keystream

解密脚本编写

理解了完整的算法逻辑后,可以编写解密脚本。

完整代码

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

MASK64 = (1 << 64) - 1
MOD = 0x13579bdf

def djb31_hash(seed: bytes) -> int:
    """DJB31哈希算法"""
    h = 1
    for b in seed:
        if b == 0:
            break
        h = (31 * h + b) & MASK64
    return h

def sext32(x: int) -> int:
    """32位符号扩展到64位"""
    x &= 0xffffffff
    if x & 0x80000000:
        return x | 0xffffffff00000000
    return x

def remuw(dividend: int, divisor: int) -> int:
    """32位无符号取模,结果符号扩展"""
    res = (dividend & 0xffffffff) % (divisor & 0xffffffff)
    return sext32(res)

def mulhu(x: int, y: int) -> int:
    """64位无符号乘法,取高64位"""
    return ((x & MASK64) * (y & MASK64) >> 64) & MASK64

def remu(x: int, d: int) -> int:
    """64位无符号取模"""
    return (x & MASK64) % (d & MASK64)

def step(a2: int, a4: int) -> tuple[int, int]:
    """密钥流生成核心步骤"""
    a2 = remuw(a2, MOD)
    a3 = remuw(a4, MOD)
    a4 = remuw(a2, MOD)
    a5 = remuw(a3, MOD)

    a2_shift = (a2 << 32) & MASK64
    a4_shift = (a4 << 32) & MASK64

    a4 = remu(mulhu(a4_shift, a2_shift), MOD)

    a3_shift = (a3 << 32) & MASK64
    a5_shift = (a5 << 32) & MASK64

    a5 = remu(mulhu(a5_shift, a3_shift), MOD)

    a2 = (a2_shift >> 32) & MASK64
    a3 = (a3_shift >> 32) & MASK64

    for _ in range(10):
        a4 = remu((a4 * a2) & MASK64, MOD)
        a5 = remu((a5 * a3) & MASK64, MOD)

    a2 = remu((a4 * a2) & MASK64, MOD)
    a4 = remu((a5 * a3) & MASK64, MOD)

    return a2, a4

def main():
    # 种子字符串
    seed = b"e4Y8YRXVzg2HRrCUy35CM0Txq91HzMGZ\x00"

    # 计算初始哈希
    h = djb31_hash(seed)

    # 初始化状态
    a2 = h >> 16
    a4 = h

    # 目标常量
    consts = [
        0x45034f63, 0x534762d2, 0x44b36d04, 0x44c3ed6a,
        0x79bb60b0, 0x42a1e767, 0x3edb7e6c, 0x30e1551d,
        0x4d3abaa4, 0x6aa29948, 0x51ce8847, 0x51623faf
    ]

    # 生成密钥流并解密
    words = []
    for i in range(6):
        a2, a4 = step(a2, a4)
        ks0 = a2 & 0xffffffff
        ks1 = a4 & 0xffffffff

        # XOR解密
        words.append(consts[2*i] ^ ks0)
        words.append(consts[2*i+1] ^ ks1)

    # 转换为字节串
    inp = b"".join(w.to_bytes(4, "little") for w in words)

    print("输入长度:", len(inp))
    print("输入内容:", inp.decode())
    print("\nflag{" + inp.decode() + "}")

if __name__ == "__main__":
    main()

运行结果

python3 solve.py

输出:

输入长度: 48
输入内容: fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX

flag{fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX}

验证解答

将求解的48字节字符串提交给原程序验证:

echo -n "fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX" | ./vvvmmm

输出:

[ASCII艺术图案]
input %48c>Good.
flag{fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX}

程序输出"Good.",验证成功。

技术总结

涉及的核心技术

1. 程序保护技术

UPX壳:

  • 识别:通过strings搜索UPX特征字符串

  • 脱壳:使用upx -d命令直接脱壳

  • 原理:压缩程序体积,增加静态分析难度

虚拟机保护:

  • Unicorn Engine实现跨架构代码模拟

  • 将核心算法编译为RISC-V指令

  • 在运行时通过虚拟机执行,隔离实际逻辑

2. RISC-V指令集

架构特点:

  • 开源精简指令集

  • 模块化设计

  • 支持多种位宽变体

关键指令:

  • mulhu:64位乘法取高位,用于高精度运算

  • remuw:32位无符号取模,结果符号扩展

  • 需要精确模拟才能正确复现算法

3. 密码学算法

DJB31哈希:

  • 简单高效的字符串哈希算法

  • 使用魔数31优化性能

  • 作为密钥派生的基础

伪随机数生成器:

  • 基于模运算和高位乘法

  • 通过迭代混淆增加复杂度

  • 生成确定性密钥流

XOR流加密:

  • 明文与密钥流逐字节异或

  • 利用XOR自反性实现加解密

  • 安全性依赖密钥流的不可预测性

4. 逆向工程技巧

分层分析:

  1. 外层:识别并去除UPX壳

  2. 中层:识别Unicorn虚拟机框架

  3. 内层:分析RISC-V算法逻辑

特征定位:

  • 字符串搜索快速定位关键技术

  • 交叉引用分析追踪数据流

  • API调用分析理解程序行为

算法还原:

  • 从汇编代码推导高级语言逻辑

  • 理解算法意图而非逐指令翻译

  • 抓住核心问题(XOR可逆性)

解题思路总结

  1. 信息收集:观察程序行为,识别文件特征

  2. 去除保护:识别UPX壳并脱壳

  3. 技术识别:通过字符串特征识别Unicorn虚拟机

  4. 架构确认:确定使用RISC-V指令集

  5. 数据提取:提取种子字符串和RISC-V字节码

  6. 算法分析:逐模块分析RISC-V代码逻辑

  7. 脚本编写:用Python复现完整算法

  8. 解密求解:利用XOR可逆性解密目标值

  9. 结果验证:用原程序验证flag正确性

关键技术难点

难点一: RISC-V指令精确模拟

RISC-V的某些指令有特殊的语义,需要准确理解:

  • mulhu返回128位乘积的高64位

  • remuw只使用低32位运算,结果符号扩展

  • 位运算的掩码处理

  • Python大整数需要手动截断

错误的指令模拟会导致密钥流完全错误,无法解密。

难点二: 数据提取

种子字符串和RISC-V代码嵌入在程序中,需要通过以下方式定位:

  • 专业工具(IDA Pro/Ghidra)的交叉引用分析

  • 动态调试在特定断点处dump内存

  • 运行时内存搜索特征模式

纯命令行工具难以完成精确定位,需要图形化逆向工具辅助。

难点三: 算法逻辑理解

需要理解三层算法的嵌套关系:

  1. DJB31哈希产生初始状态

  2. 伪随机数生成器产生密钥流

  3. XOR流加密验证输入

理解各层的数学原理和密码学意义,才能找到正确的解题方向。

防御与攻击视角

从防御角度:

这道题展示了多层保护的思路:

  • UPX壳作为第一道防线

  • 虚拟机保护隐藏真实逻辑

  • 非常见架构(RISC-V)增加分析难度

  • 复杂数学运算混淆算法

改进建议:

  • 使用修改过的UPX壳防止直接脱壳

  • 加入反调试检测

  • 在虚拟机层面增加代码混淆

  • 使用更强的加密算法(如AES)

从攻击角度:

突破点:

  • UPX为标准壳,可直接脱壳

  • Unicorn有明确的API特征

  • XOR流加密可逆

  • 算法逻辑相对清晰

关键:

  • 善用专业工具(IDA Pro)

  • 理解底层架构(RISC-V)

  • 掌握密码学基础

  • 编程实现能力

学习要点

对初学者的建议

工具掌握:

  • 基础:file, strings, readelf等Linux工具

  • 脱壳:upx, UPX等专用工具

  • 反汇编:IDA Pro, Ghidra, radare2

  • 调试:GDB, QEMU用户模式模拟

  • 编程:Python脚本编写能力

知识储备:

  • 多架构知识:x86-64, ARM, RISC-V等

  • 密码学基础:哈希,对称加密,流加密

  • 程序保护:加壳,虚拟机,混淆

  • 汇编语言:至少掌握一种架构

分析方法:

  • 从易到难:先处理外层保护

  • 特征识别:利用已知模式快速定位

  • 动静结合:静态分析+动态调试

  • 抓住本质:理解算法意图

思维方式:

  • 不被表面复杂度吓倒

  • 分解问题,各个击破

  • 善用现有工具和资源

  • 注重验证,确保正确性

进阶技巧

虚拟机保护分析:

  • 识别不同虚拟机框架(Unicorn, QEMU, 自研VM)

  • 理解虚拟机API和调用约定

  • 提取虚拟机字节码的方法

  • 编写虚拟机字节码的反汇编器

多架构逆向:

  • 熟悉常见架构的指令集和ABI

  • 理解不同架构的调用约定

  • 掌握交叉编译和模拟执行

  • 使用QEMU用户模式运行其他架构程序

密码学在CTF中的应用:

  • 识别常见加密算法(AES, RSA, DES等)

  • 分析自定义加密算法的弱点

  • 利用数学性质(如XOR可逆性)

  • 掌握基本的密码分析技巧

结语

vvvmmm是一道设计精良的综合性逆向题目,融合了程序保护、虚拟机技术、RISC-V架构和密码学算法等多个技术领域。通过系统的分层分析:

  1. 识别UPX壳并成功脱壳

  2. 发现Unicorn虚拟机保护

  3. 确认RISC-V指令集

  4. 提取关键数据(种子和字节码)

  5. 逐模块分析算法逻辑

  6. 编写解密脚本求解

  7. 验证结果正确性

我们最终获得了正确的flag:

flag{fANUES0XtUXBDEbOXs4xFcXDb3Q5kMU87bZLMZJfuRnCvfwX}

这道题目展示了现代CTF逆向题的典型特征:多层保护、跨架构技术、算法复杂度。通过分析这道题,我们不仅学习了具体的技术点,更重要的是掌握了系统化的逆向分析方法论。

对于逆向工程学习者,这道题提供了很好的实践机会:

  • 接触真实的程序保护技术

  • 学习新兴的RISC-V架构

  • 理解虚拟机保护原理

  • 提升密码学算法分析能力

  • 锻炼问题分解和解决能力

希望本文的详细分析能够帮助读者理解这道题目的每个技术细节,并在今后的CTF比赛和安全研究中应用这些知识和方法。

逆向工程是一个需要持续学习和实践的领域。保持好奇心,不断挑战新的技术,善于总结方法论,终将在这个领域取得进步。


文章来源: https://www.freebuf.com/articles/others-articles/465701.html
如有侵权请联系:admin#unsafe.sh