找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1615

积分

1

好友

227

主题
发表于 6 天前 | 查看: 21| 回复: 0

本文将指导你构建一个适用于Windows平台下x86/x64 CPU环境的静态分析框架。我们将探讨该框架的核心流程,并与IDA Pro的基础功能进行对比。这个框架是实现诸如空闲寄存器分析、基本块分析、控制流图(CFG)分析、代码混淆乃至污点分析等高级静态分析功能的基础。

本文聚焦于三个核心模块的构建:

  1. 指令流构建 (buildInstFlow)
  2. 基本块构建 (buildBasicBlock)
  3. 控制流图构建 (buildCFG)

在深入实现之前,需要了解一些基本概念,这对理解后续的算法与数据结构设计至关重要。

必要概念

控制流图示例

CFG (控制流图)

控制流图是程序分析和编译器优化中的基础数据结构,用于表示程序执行过程中所有可能的控制流路径。它将程序的执行流程抽象为一个有向图,其中节点代表基本块边代表控制流转移

形式化定义为一个有向图 G = (N, E, Entry, Exit),其中:

  • N:节点集合,每个节点代表一个基本块
  • E ⊆ N × N:边集合,表示控制流转移关系
  • Entry:唯一的入口节点
  • Exit:一个或多个出口节点
BasicBlock (基本块)

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

  • 单入口:只能从第一条指令进入
  • 单出口:只能从最后一条指令离开
  • 原子执行:要么全部执行,要么都不执行

一个基本块 BB 是一个指令序列 {i₁, i₂, ..., iₙ},满足:

  • 单入口性质:只有 i₁ 可以作为执行的起点
  • 单出口性质:只有 iₙ 可以将控制流转移到其他基本块
  • 顺序执行性质:i₁ → i₂ → ... → iₙ 顺序执行,中间无分支
基本块划分原则

划分规律如下:

  • 程序的第一条指令
  • 任何跳转指令的目标指令
  • 紧跟在跳转指令后面的指令
IDA F5 原理浅析

虽然本文的框架看似与IDA的F5反编译功能相去甚远,但实则完成了F5功能的第一步。IDA通常遵循以下步骤:

  1. PE结构扫描分析
  2. 函数识别
  3. 将汇编提升为平台无关的 LLIL (低级中间语言)
  4. 将 LLIL 提升至 MLIL (中级中间语言),并进行函数及参数解析
  5. 优化并根据上一步结果进行语义翻译,生成伪代码

本文设计的框架采用了非SSA的标记汇编作为简化的LLIL表示(严格意义上并非标准LLIL,仅为教学方便),并完成了上述第3步,同时构建了CFG和基本块划分。

LLIL (低级中间语言)

LLIL是一种用于程序分析和逆向工程的中间表示形式。它将机器码抽象为更易于分析的形式,同时保留了底层的语义细节,其核心目标之一是架构无关性。

如何设计 LowLevelIL

关于设计LLIL,可以总结为五个精炼原则:

  1. 语义明确性
  2. 架构无关性
  3. 结构统一性
  4. 信息完整性
  5. 可验证性

在本文的简化设计中,我们暂舍弃架构无关性。以下给出一个简化的IL设计格式。在实际构建中,可以利用Zydis、Capstone等反汇编框架来辅助实现,这比手动实现复杂的转换引擎更具可操作性。

; L_xxx 是用于标记指令地址的常规标签
L_xxx:
    mov eax, ebx
; J_xxx 是跳转目标的标签
J_xxx:
    mov eax, ebx

通过常见的反汇编引擎可以轻易实现上述标签标记。处理之后,即可将反汇编文件格式转换为我们需要的LLIL表示。

反汇编标记示例

框架流程与对象设计

首先需要明确静态分析所需的核心对象并进行设计,主要包括:

  1. 指令对象 CBasicInst
  2. 基本块对象 CBasicBlock

以下是示例代码:

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;
    /* 空闲寄存器集合,用于活变量分析 */
    std::set<std::string> m_setFreeReg;
    /* 跳转目标地址 */
    std::string m_strJmpTarget;

    /* 判断是否为基本块结束语句 */
    bool isBlockTerminator() const {
        return type == InstType::BRANCH ||
               type == InstType::JUMP ||
               type == InstType::RET;
    }
};

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();
    }
};

框架流程严格按照上文所述依次构建:指令流 -> 基本块 -> 控制流图。以下是核心流程的示意图:

指令流构建 (buildInstFlow)

指令流构建流程

基本块构建 (buildBasicBlock)

基本块构建流程

控制流图构建 (buildCFG)

控制流图构建流程

原始指令流提取

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;
}

构建BasicBlock

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;
}

重建CFG

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)的范畴
            break;
        }
        case InstType::RET:  // 返回指令:没有后继(过程内)
        {
            // RET 指令在过程内CFG中没有后继块
            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;
}

输出绘制

以下CFG输出代码示例使用Mermaid语法生成图表。请注意,核心算法建议手动实现,采用“注释驱动编码”的方式有助于提升编码能力。

void CAssemblyScanner::dbgPrintCFG() {
    std::ofstream outFile("CFG.md");
    if (!outFile.is_open()) {
        std::cerr << "Error: Cannot create CFG.md" << std::endl;
        return;
    }
    // 写入 Mermaid 文件头
    outFile << "# Control Flow Graph\n\n";
    outFile << "```mermaid\n";
    outFile << "flowchart TD\n";

    // 遍历所有基本块,定义节点
    for (size_t i = 0; i < m_vecBlocks.size(); ++i) {
        const auto& block = m_vecBlocks[i];
        std::string blockLabel = block->m_strBlockLabel;
        // 转义特殊字符
        std::string safeLabel = blockLabel;
        std::replace(safeLabel.begin(), safeLabel.end(), '_', ' ');

        // 获取最后一条指令类型
        const CBasicInst* lastInst = block->getLastInst();
        std::string instTypeStr = "NORMAL";
        if (lastInst) {
            switch (lastInst->type) {
            case InstType::BRANCH:  instTypeStr = "BRANCH"; break;
            case InstType::JUMP:    instTypeStr = "JUMP"; break;
            case InstType::CALL:    instTypeStr = "CALL"; break;
            case InstType::RET:     instTypeStr = "RET"; break;
            default:                instTypeStr = "NORMAL"; break;
            }
        }
        // 定义节点:显示块标签、指令数量、类型
        outFile << "    " << blockLabel << "[\"" << safeLabel
                << "<br/>Instructions: " << block->m_instructions.size()
                << "<br/>Type: " << instTypeStr << "\"]\n";

        // 为不同类型的块设置不同样式
        if (lastInst) {
            if (lastInst->type == InstType::RET) {
                outFile << "    style " << blockLabel << " fill:#f99\n";  // 返回块用红色
            } else if (lastInst->type == InstType::BRANCH) {
                outFile << "    style " << blockLabel << " fill:#9f9\n";  // 条件跳转用绿色
            } else if (lastInst->type == InstType::JUMP) {
                outFile << "    style " << blockLabel << " fill:#99f\n";  // 无条件跳转用蓝色
            }
        }
        // 标记入口块
        if (i == 0) {
            outFile << "    style " << blockLabel << " fill:#ff9,stroke:#333,stroke-width:4px\n";
        }
    }
    outFile << "\n";
    // 遍历所有基本块,输出边(后继关系)
    for (const auto& block : m_vecBlocks) {
        const CBasicInst* lastInst = block->getLastInst();
        for (const auto& successor : block->successors) {
            // 判断边类型并标注
            if (lastInst && lastInst->type == InstType::BRANCH) {
                // 条件跳转:区分跳转边和fallthrough边
                if (!lastInst->m_strJmpTarget.empty()) {
                    CBasicBlock* targetBlock = findBlockByLabel(lastInst->m_strJmpTarget);
                    if (targetBlock && targetBlock->m_strBlockLabel == successor) {
                        // 跳转边 (taken)
                        outFile << "    " << block->m_strBlockLabel
                                << " -->|\"jump (taken)\"| " << successor << "\n";
                    } else {
                        // fallthrough边 (not taken)
                        outFile << "    " << block->m_strBlockLabel
                                << " -->|\"fallthrough\"| " << successor << "\n";
                    }
                } else {
                    outFile << "    " << block->m_strBlockLabel
                            << " --> " << successor << "\n";
                }
            } else if (lastInst && lastInst->type == InstType::JUMP) {
                // 无条件跳转
                outFile << "    " << block->m_strBlockLabel
                        << " -->|\"jump\"| " << successor << "\n";
            } else {
                // 普通顺序执行
                outFile << "    " << block->m_strBlockLabel
                        << " --> " << successor << "\n";
            }
        }
    }
    outFile << "```\n\n";
    // 输出统计信息和块详情...
    outFile.close();
    std::cout << "CFG exported to CFG.md" << std::endl;
}

框架效果展示

最后,让我们将框架的分析效果与IDA 9.0生成的基础CFG图进行直观对比。

与IDA CFG对比图

基本块的构建结果示例:
基本块构建效果

初始指令流的构建结果示例:
指令流构建效果

通过实现这样一个静态分析框架,我们不仅深入理解了网络与系统底层程序分析的基本原理(如控制流图的构建),也掌握了构建类似IDA基础功能的核心算法与数据结构,为进一步探索高级逆向工程分析技术打下了坚实基础。




上一篇:Chrony时钟同步故障排查指南:解决分布式系统时间漂移问题
下一篇:Chrome与Edge“精选”VPN扩展被曝窃取用户AI聊天记录
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 21:11 , Processed in 0.621339 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表