首页
社区
课程
招聘
[原创]实现一个基于LLIL的x86/x64的静态分析框架
发表于: 2025-11-21 09:14 4683

[原创]实现一个基于LLIL的x86/x64的静态分析框架

2025-11-21 09:14
4683

大家好,我是TeddyBe4r,前几期关于静态分析的文章热度不是很高,论坛里研究的同门还是太少遂决定发一期实战,这篇文章将会浅尝辄止的为各位构建出一个Windows平台下 x86/x64 CPU环境下的一个静态分析框架,并且会与IDA的基础功能进行比较,该框架仅仅是静态分析的基础如果想要在此基础上进行更多的探索则需要各位自己去完成,这个框架能干的事情非常多,譬如空闲寄存器分析,基本块分析,CFG分析,代码混淆,甚至是污点分析,我已经完成了整个框架的完整版并将他彻底构建为飘雪十二剑(我研发的一个闭源Rootkit远控平台)基础编译工具链的一个模块,但是我不能将他彻底开源(自己动手丰衣足食)我只能提取出一部分核心内容经过简化后供大家研究。这篇文章首先带领大家研究3个核心的主题

在深入之前希望大家能够了解一些基本概念,如果大家对于理论知识感兴趣可以前往我前几期的文章查看。

CFG又称控制流图(CFG - Control Flow Graph)控制流图是程序分析和编译器优化中的基础数据结构,用于表示程序执行过程中所有可能的控制流路径。它将程序的执行流程抽象为一个有向图,其中节点代表基本块(Basic Block)边代表控制流转移
形式化定义
CFG 是一个有向图 G = (N, E, Entry, Exit),其中:
N:节点集合,每个节点代表一个基本块
E ⊆ N × N:边集合,表示控制流转移关系
Entry:唯一的入口节点
Exit:一个或多个出口节点

基本块是编译器和程序分析中的基本单元,是一段顺序执行的代码序列,具有以下特点:

基本块是构建控制流图(CFG)的基础单元,也是编译器优化的基本单位。
一个基本块 BB 是一个指令序列 {i₁, i₂, ..., iₙ},满足:

基本块划分我这里再次给出划分规律,这个在我上期的静态分析文章中有提到过。

虽然这篇文章看上去和神秘的F5功能相差甚远实则不然,该框架做出了F5功能的第一步。在这之前我们需要了解下IDAF5的原理,IDA一般遵守如下步骤

而我们这篇文章设计采用了非SSA的标记汇编作为LLIL(严格意义上不算是LLIL不过为了方便理解和教学就使用了这种形式),并且完成了第3步,并构建了CFG和基本块划分

LLIL (Low-Level Intermediate Language) 是低级中间语言,是程序分析和逆向工程中的一种中间表示形式。它将机器码抽象为更易于分析的形式,同时保留了底层的语义细节。

关于设计LLIL我给出5条最为精炼的总结

但是在文章中为了简化设计我们舍弃掉架构无关,我这里给出和完整版框架中不同的IL设计格式,大家在实际构建的过程中可以借助 Zydis,Capstone等反汇编框架完成该步骤的设计,这样可操作性更强方便大家动手而不需要去实现一个复杂的转换引擎。

如上步骤可以通过常见的反汇编引擎轻易实现,给出一个实现后的样子
图片描述
经过这样的处理之后我们即可把该反汇编后的文件格式转换为我们需要的LLIL了。

我们要明确静态分析中需要的对象有哪些并且对此做出设计,我将列出所需的对象列表

直接给出示例代码

框架流程首先是严格按照上文所述依次构建分别是,下面我将给出最为核心的代码都标有详细的注释供大家阅读学习。首先给出流程掌握大体的结构。

图片描述

图片描述

图片描述

输出绘制我们就给出CFG的实现把其他实现都很简单动动手就可以实现,注意CFG输出代码是AI生成的,我不推荐大家使用AIGC,可以帮忙干杂活但是核心的算法一定要手动实现,先注释后代码,注释驱动代码,会让你的编码水平得到提升

然后给大家看看框架的效果吧,我们就直接和IDA9.0的基础CFG图的形式进行对比
图片描述
基本块的构建
图片描述
初始指令流的构建
图片描述

; 这里是常规标签主要是用于标记指令标签的地址信息
L_xxx:
    mov eax, ebx
; J_xxx 是跳转目标的标签
J_xxx:
    mov eax, ebx
; 这里是常规标签主要是用于标记指令标签的地址信息
L_xxx:
    mov eax, ebx
; J_xxx 是跳转目标的标签
J_xxx:
    mov eax, ebx
enum class InstType {
    NORMAL,      // 普通指令
    BRANCH,      // Jxx
    JUMP,        // 无条件跳转 (jmp)
    CALL,        // 函数调用
    RET          // 返回指令
};
 
 
class CBasicInst
{
public:
    CBasicInst() : type(InstType::NORMAL) {}
 
 
    /* 汇编代码 mov eax, ebx */
    std::string m_strAsmCode;
 
    /* 操作码 mov je jb */
    std::string m_strOpcode;
 
    /* 操作数 eax, ebx*/
    std::string m_strOperands;
 
    /* 指令标签 */
    std::string m_strLabel;
 
    /* 指令类型 */
    InstType type;
 
    /* todo 空闲寄存器 活变量分析的时候使用的 */
    std::set<std::string> m_setFreeReg;
 
 
    /* 跳转地址 */
    std::string m_strJmpTarget;
 
    /* 是否为结束语句 */
    bool isBlockTerminator() const {
        return type == InstType::BRANCH ||
            type == InstType::JUMP ||
            type == InstType::RET;
    }
 
};
 
 
 
class CBasicJmpInst : public CBasicInst
{
 
    CBasicJmpInst()
    {
        type = InstType::BRANCH;
 
    };
public:
    std::string m_strJmpTarget;
};
 
 
 
class CBasicBlock
{
public:
    /* 块起始标签
       这里块标签我们取第一个指令的标签即可。
     
    */
    std::string m_strBlockLabel;
 
    /* 指令序列 */
    std::vector<CBasicInst> m_instructions;
 
    /* 后继块标签 */
    std::set<std::string> successors;     
 
    /* 前驱块标签 */
    std::set<std::string> predecessors;   
 
    /* 添加指令 */
    void addInstruction(const CBasicInst& inst) {
        m_instructions.push_back(inst);
    }
 
    /* 获取第一条指令标签 */
    std::string getStartLabel() const {
        return m_instructions.empty() ? "" : m_instructions.front().m_strLabel;
    }
     
    /* 获取块标签 */
    std::string getBlockLable() const
    {
        return getStartLabel();
    }
 
    // 获取最后一条指令
    const CBasicInst* getLastInst() const {
        return m_instructions.empty() ? nullptr : &m_instructions.back();
    }
 
    /* 获取块大小 */
    unsigned int getBlockSize()
    {
        return m_instructions.size();
    }
 
    /* 空块 */
    bool isEmpty()
    {
        return m_instructions.empty();
    }
 
};
enum class InstType {
    NORMAL,      // 普通指令
    BRANCH,      // Jxx
    JUMP,        // 无条件跳转 (jmp)
    CALL,        // 函数调用
    RET          // 返回指令
};
 
 
class CBasicInst
{
public:
    CBasicInst() : type(InstType::NORMAL) {}
 
 
    /* 汇编代码 mov eax, ebx */
    std::string m_strAsmCode;
 
    /* 操作码 mov je jb */
    std::string m_strOpcode;
 
    /* 操作数 eax, ebx*/
    std::string m_strOperands;
 
    /* 指令标签 */
    std::string m_strLabel;
 
    /* 指令类型 */
    InstType type;
 
    /* todo 空闲寄存器 活变量分析的时候使用的 */
    std::set<std::string> m_setFreeReg;
 
 
    /* 跳转地址 */
    std::string m_strJmpTarget;
 
    /* 是否为结束语句 */
    bool isBlockTerminator() const {
        return type == InstType::BRANCH ||
            type == InstType::JUMP ||
            type == InstType::RET;
    }
 
};
 
 
 
class CBasicJmpInst : public CBasicInst
{
 
    CBasicJmpInst()
    {
        type = InstType::BRANCH;
 
    };
public:
    std::string m_strJmpTarget;
};
 
 
 
class CBasicBlock
{
public:
    /* 块起始标签
       这里块标签我们取第一个指令的标签即可。
     
    */
    std::string m_strBlockLabel;
 
    /* 指令序列 */
    std::vector<CBasicInst> m_instructions;
 
    /* 后继块标签 */
    std::set<std::string> successors;     
 
    /* 前驱块标签 */
    std::set<std::string> predecessors;   
 
    /* 添加指令 */
    void addInstruction(const CBasicInst& inst) {
        m_instructions.push_back(inst);
    }
 
    /* 获取第一条指令标签 */
    std::string getStartLabel() const {
        return m_instructions.empty() ? "" : m_instructions.front().m_strLabel;
    }
     
    /* 获取块标签 */
    std::string getBlockLable() const
    {
        return getStartLabel();
    }
 
    // 获取最后一条指令
    const CBasicInst* getLastInst() const {
        return m_instructions.empty() ? nullptr : &m_instructions.back();
    }
 
    /* 获取块大小 */
    unsigned int getBlockSize()
    {
        return m_instructions.size();
    }
 
    /* 空块 */
    bool isEmpty()
    {
        return m_instructions.empty();
    }
 
};
bool CAssemblyScanner::buildInstFlow()
{
 
 
    if (m_strFileName == "")
    {
        std::cerr << "Error: Filename not empty " << m_strFileName << std::endl;
        return false;
    }
 
 
    /* 构建行 */
    std::vector<std::string> lines;
    if (!readShellcodeSection(lines))
    {
        return false;
    }
 
    /* 通过行构建原始指令流 */
    m_vecRawInsts.clear();
    std::string currentLabel = "";  // 当前待分配的标签
 
    for (auto& line : lines)
    {
        std::string trimmedLine = trim(line);
        if (trimmedLine.empty()) continue;
 
        /* 判断是否为标签行:冒号必须在行尾 */
        if (!trimmedLine.empty() && trimmedLine.back() == ':')
        {
            // 提取标签(去除末尾冒号)
            currentLabel = trimmedLine.substr(0, trimmedLine.length() - 1);
            currentLabel = trim(currentLabel);  // 去除可能的空白
        }
        else
        {
            /* 提取指令 */
            CBasicInst inst;
            if (parseInstruction(trimmedLine, inst))
            {
                inst.m_strLabel = currentLabel;  
                inst.m_strAsmCode = trimmedLine;
                m_vecRawInsts.push_back(inst);
                currentLabel = "";
            }
        }
    }
    m_bParsed = true;
    return true;
}
bool CAssemblyScanner::buildInstFlow()
{
 
 
    if (m_strFileName == "")
    {
        std::cerr << "Error: Filename not empty " << m_strFileName << std::endl;
        return false;
    }
 
 
    /* 构建行 */
    std::vector<std::string> lines;
    if (!readShellcodeSection(lines))
    {
        return false;
    }
 
    /* 通过行构建原始指令流 */
    m_vecRawInsts.clear();
    std::string currentLabel = "";  // 当前待分配的标签
 
    for (auto& line : lines)
    {
        std::string trimmedLine = trim(line);
        if (trimmedLine.empty()) continue;
 
        /* 判断是否为标签行:冒号必须在行尾 */
        if (!trimmedLine.empty() && trimmedLine.back() == ':')
        {
            // 提取标签(去除末尾冒号)
            currentLabel = trimmedLine.substr(0, trimmedLine.length() - 1);
            currentLabel = trim(currentLabel);  // 去除可能的空白
        }
        else
        {
            /* 提取指令 */
            CBasicInst inst;
            if (parseInstruction(trimmedLine, inst))
            {
                inst.m_strLabel = currentLabel;  
                inst.m_strAsmCode = trimmedLine;
                m_vecRawInsts.push_back(inst);
                currentLabel = "";
            }
        }
    }
    m_bParsed = true;
    return true;
}
bool CAssemblyScanner::buildBasicBlock()
{
    if (!m_bParsed)
    {
        std::cerr << "Error: Must call buildInstFlow() first!" << std::endl;
        return false;
    }
 
    if (m_vecRawInsts.empty())
    {
        std::cerr << "Error: No instructions to build blocks" << std::endl;
        return false;
    }
 
    // ========== 第一步:收集所有跳转目标标签 ==========
    std::set<std::string> jumpTargets;
 
    for (const auto& inst : m_vecRawInsts)
    {
        // 收集所有跳转、分支、调用指令的目标
        if ((inst.type == InstType::JUMP ||
            inst.type == InstType::BRANCH ||
            inst.type == InstType::CALL) &&
            !inst.m_strJmpTarget.empty())
        {
            jumpTargets.insert(inst.m_strJmpTarget);
        }
    }
 
    // ========== 第二步:标记基本块边界 ==========
    std::set<size_t> blockStarts;
 
    // 规则1:第一条指令是基本块入口
    blockStarts.insert(0);
 
    for (size_t i = 0; i < m_vecRawInsts.size(); ++i)
    {
        const auto& inst = m_vecRawInsts[i];
 
        // 规则2:跳转目标地址是基本块入口
        if (!inst.m_strLabel.empty() && jumpTargets.count(inst.m_strLabel) > 0)
        {
            blockStarts.insert(i);
        }
 
        // 规则3:紧跟跳转指令的指令是基本块入口
        if (inst.isBlockTerminator() && i + 1 < m_vecRawInsts.size())
        {
            blockStarts.insert(i + 1);
        }
    }
 
    // ========== 第三步:按边界切分指令流 ==========
    m_vecBlocks.clear();
 
    std::vector<size_t> starts(blockStarts.begin(), blockStarts.end());
 
    for (size_t i = 0; i < starts.size(); ++i)
    {
        size_t startIdx = starts[i];
        size_t endIdx = (i + 1 < starts.size()) ? starts[i + 1] : m_vecRawInsts.size();
 
        auto block = std::make_unique<CBasicBlock>();
 
        // 将指令添加到块中
        for (size_t j = startIdx; j < endIdx; ++j)
        {
            block->addInstruction(m_vecRawInsts[j]);
        }
 
        // 设置块标签:优先使用跳转目标标签
        if (!block->m_instructions.empty())
        {
            std::string labelToUse = "";
 
            // 查找是否有跳转目标标签
            for (const auto& inst : block->m_instructions)
            {
                if (!inst.m_strLabel.empty() && jumpTargets.count(inst.m_strLabel) > 0)
                {
                    labelToUse = inst.m_strLabel;
                    break;
                }
            }
 
            // 如果没有,使用第一条指令的标签
            if (labelToUse.empty() && !block->m_instructions[0].m_strLabel.empty())
            {
                labelToUse = block->m_instructions[0].m_strLabel;
            }
 
            // 如果还是没有,生成块标签
            if (labelToUse.empty())
            {
                labelToUse = "BB_" + std::to_string(m_vecBlocks.size());
            }
 
            block->m_strBlockLabel = labelToUse;
        }
 
        m_vecBlocks.push_back(std::move(block));
    }
 
    // ========== 第四步:建立标签索引 ==========
    m_mapLabelToBlock.clear();
    m_mapLabelToInst.clear();
 
    for (auto& block : m_vecBlocks)
    {
        // 块标签 -> 块
        m_mapLabelToBlock[block->m_strBlockLabel] = block.get();
 
        // 所有指令标签 -> 块和指令
        for (auto& inst : block->m_instructions)
        {
            if (!inst.m_strLabel.empty())
            {
                m_mapLabelToInst[inst.m_strLabel] = &inst;
                m_mapLabelToBlock[inst.m_strLabel] = block.get();
            }
        }
    }
 
    std::cout << "Built " << m_vecBlocks.size() << " basic blocks" << std::endl;
    return true;
}
bool CAssemblyScanner::buildBasicBlock()
{
    if (!m_bParsed)
    {
        std::cerr << "Error: Must call buildInstFlow() first!" << std::endl;
        return false;
    }
 
    if (m_vecRawInsts.empty())
    {
        std::cerr << "Error: No instructions to build blocks" << std::endl;
        return false;
    }
 
    // ========== 第一步:收集所有跳转目标标签 ==========
    std::set<std::string> jumpTargets;
 
    for (const auto& inst : m_vecRawInsts)
    {
        // 收集所有跳转、分支、调用指令的目标
        if ((inst.type == InstType::JUMP ||
            inst.type == InstType::BRANCH ||
            inst.type == InstType::CALL) &&
            !inst.m_strJmpTarget.empty())
        {
            jumpTargets.insert(inst.m_strJmpTarget);
        }
    }
 
    // ========== 第二步:标记基本块边界 ==========
    std::set<size_t> blockStarts;
 
    // 规则1:第一条指令是基本块入口
    blockStarts.insert(0);
 
    for (size_t i = 0; i < m_vecRawInsts.size(); ++i)
    {
        const auto& inst = m_vecRawInsts[i];
 
        // 规则2:跳转目标地址是基本块入口
        if (!inst.m_strLabel.empty() && jumpTargets.count(inst.m_strLabel) > 0)
        {
            blockStarts.insert(i);
        }
 
        // 规则3:紧跟跳转指令的指令是基本块入口
        if (inst.isBlockTerminator() && i + 1 < m_vecRawInsts.size())
        {
            blockStarts.insert(i + 1);
        }
    }
 
    // ========== 第三步:按边界切分指令流 ==========
    m_vecBlocks.clear();
 
    std::vector<size_t> starts(blockStarts.begin(), blockStarts.end());
 
    for (size_t i = 0; i < starts.size(); ++i)
    {
        size_t startIdx = starts[i];
        size_t endIdx = (i + 1 < starts.size()) ? starts[i + 1] : m_vecRawInsts.size();
 
        auto block = std::make_unique<CBasicBlock>();
 
        // 将指令添加到块中
        for (size_t j = startIdx; j < endIdx; ++j)
        {
            block->addInstruction(m_vecRawInsts[j]);
        }
 
        // 设置块标签:优先使用跳转目标标签
        if (!block->m_instructions.empty())
        {
            std::string labelToUse = "";
 
            // 查找是否有跳转目标标签
            for (const auto& inst : block->m_instructions)
            {
                if (!inst.m_strLabel.empty() && jumpTargets.count(inst.m_strLabel) > 0)
                {
                    labelToUse = inst.m_strLabel;
                    break;
                }
            }
 
            // 如果没有,使用第一条指令的标签
            if (labelToUse.empty() && !block->m_instructions[0].m_strLabel.empty())
            {
                labelToUse = block->m_instructions[0].m_strLabel;
            }
 
            // 如果还是没有,生成块标签
            if (labelToUse.empty())
            {
                labelToUse = "BB_" + std::to_string(m_vecBlocks.size());
            }
 
            block->m_strBlockLabel = labelToUse;
        }
 
        m_vecBlocks.push_back(std::move(block));
    }
 
    // ========== 第四步:建立标签索引 ==========
    m_mapLabelToBlock.clear();
    m_mapLabelToInst.clear();
 
    for (auto& block : m_vecBlocks)
    {
        // 块标签 -> 块
        m_mapLabelToBlock[block->m_strBlockLabel] = block.get();
 
        // 所有指令标签 -> 块和指令
        for (auto& inst : block->m_instructions)
        {
            if (!inst.m_strLabel.empty())
            {
                m_mapLabelToInst[inst.m_strLabel] = &inst;
                m_mapLabelToBlock[inst.m_strLabel] = block.get();
            }
        }
    }
 
    std::cout << "Built " << m_vecBlocks.size() << " basic blocks" << std::endl;
    return true;
}
bool CAssemblyScanner::buildCFG()
{
    if (m_vecBlocks.empty())
    {
        std::cerr << "Error: Must call buildBasicBlock() first!" << std::endl;
        return false;
    }
 
    /* 清空后继关系 */
    for (auto& block : m_vecBlocks)
    {
        block->successors.clear();
        block->predecessors.clear();
    }
 
    /* 遍历基本块分析控制流 */
    for (size_t i = 0; i < m_vecBlocks.size(); ++i)
    {
        auto& currentBlock = m_vecBlocks[i];
        const CBasicInst* lastInst = currentBlock->getLastInst();
 
        if (!lastInst)
        {
            /* 空快 跳过 */
            continue
        }
 
        // 根据最后一条指令的类型建立后继关系
        switch (lastInst->type)
        {
        case InstType::BRANCH:  // 条件跳转:有两个后继
        {
            // 后继1:跳转目标块
            if (!lastInst->m_strJmpTarget.empty())
            {
                CBasicBlock* targetBlock = findBlockByLabel(lastInst->m_strJmpTarget);
                if (targetBlock)
                {
                    currentBlock->successors.insert(targetBlock->m_strBlockLabel);
                    targetBlock->predecessors.insert(currentBlock->m_strBlockLabel);
                }
                else
                {
                    std::cerr << "Warning: Branch target not found: "
                        << lastInst->m_strJmpTarget << std::endl;
                }
            }
 
            // 后继2:fallthrough 到下一个块
            if (i + 1 < m_vecBlocks.size())
            {
                auto& nextBlock = m_vecBlocks[i + 1];
                currentBlock->successors.insert(nextBlock->m_strBlockLabel);
                nextBlock->predecessors.insert(currentBlock->m_strBlockLabel);
            }
            break;
        }
 
        case InstType::JUMP:  // 无条件跳转:只有一个后继
        {
            if (!lastInst->m_strJmpTarget.empty())
            {
                CBasicBlock* targetBlock = findBlockByLabel(lastInst->m_strJmpTarget);
                if (targetBlock)
                {
                    currentBlock->successors.insert(targetBlock->m_strBlockLabel);
                    targetBlock->predecessors.insert(currentBlock->m_strBlockLabel);
                }
                else
                {
                    std::cerr << "Warning: Jump target not found: "
                        << lastInst->m_strJmpTarget << std::endl;
                }
            }
            break;
        }
 
        case InstType::CALL:  // 函数调用:通常返回到下一个块
        {
            // CALL 指令通常会返回,后继是下一个块
            if (i + 1 < m_vecBlocks.size())
            {
                auto& nextBlock = m_vecBlocks[i + 1];
                currentBlock->successors.insert(nextBlock->m_strBlockLabel);
                nextBlock->predecessors.insert(currentBlock->m_strBlockLabel);
            }
            // 注意:这里不处理 CALL 的目标函数,因为那是过程间分析 对于过程间分析需要重建ICFG这里仅仅做出CFG
            break;
        }
 
        case InstType::RET:  // 返回指令:没有后继
        {
            // RET 指令没有后继块
            break;
        }
 
        case InstType::NORMAL:  // 普通指令:顺序执行到下一个块
        {
            if (i + 1 < m_vecBlocks.size())
            {
                auto& nextBlock = m_vecBlocks[i + 1];
                currentBlock->successors.insert(nextBlock->m_strBlockLabel);
                nextBlock->predecessors.insert(currentBlock->m_strBlockLabel);
            }
            break;
        }
 
        default:
            break;
        }
    }
 
    std::cout << "CFG built successfully with " << m_vecBlocks.size()
        << " blocks" << std::endl;
 
    return true;
}
bool CAssemblyScanner::buildCFG()
{
    if (m_vecBlocks.empty())
    {
        std::cerr << "Error: Must call buildBasicBlock() first!" << std::endl;
        return false;
    }
 
    /* 清空后继关系 */
    for (auto& block : m_vecBlocks)
    {
        block->successors.clear();
        block->predecessors.clear();

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 48
支持
分享
最新回复 (14)
雪    币: 743
活跃值: (2642)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
mark
2025-11-21 18:11
0
雪    币: 1400
活跃值: (6918)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
3
1
2025-11-21 18:19
0
雪    币: 144
活跃值: (2063)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
mark
2025-11-22 15:51
0
雪    币: 10
活跃值: (2358)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
5
888
2025-11-23 18:12
0
雪    币: 138
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
111111111
2025-11-24 10:17
0
雪    币: 1100
活跃值: (65)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
mark
2025-11-24 15:02
0
雪    币: 3262
活跃值: (5719)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
mark
2025-11-24 15:37
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
2025-11-25 15:55
0
雪    币: 2790
活跃值: (5607)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
10
感谢分享
2025-11-26 11:32
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
mark
2025-11-26 20:55
0
雪    币: 222
活跃值: (697)
能力值: ( LV2,RANK:16 )
在线值:
发帖
回帖
粉丝
12
mk
2025-11-27 16:49
0
雪    币: 40
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
学习了
2025-11-29 18:32
0
雪    币: 1908
活跃值: (7043)
能力值: ( LV7,RANK:118 )
在线值:
发帖
回帖
粉丝
14
mk
2025-12-3 18:20
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
2025-12-28 23:54
0
游客
登录 | 注册 方可回帖
返回